venusian-1.0a8/0000775000175000017500000000000012132727372014237 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/0000775000175000017500000000000012132727372016067 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/compat/0000775000175000017500000000000012132727372017352 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/compat/__init__.py0000644000175000017500000000063111714637044021462 0ustar chrismchrism00000000000000import sys PY3 = sys.version_info[0] == 3 try: # pragma: no cover from pkgutil import iter_modules except ImportError: # pragma: no cover from pkgutil_26 import iter_modules if PY3: # pragma: no cover def is_nonstr_iter(v): if isinstance(v, str): return False return hasattr(v, '__iter__') else: def is_nonstr_iter(v): return hasattr(v, '__iter__') venusian-1.0a8/venusian/compat/pkgutil_26.py0000644000175000017500000005271211622562555021722 0ustar chrismchrism00000000000000"""Utilities to support packages.""" # NOTE: This module must remain compatible with Python 2.3, as it is shared # by setuptools for distribution with Python 2.3 and up. if 1: # pragma: no cover import os import sys import imp import os.path from types import ModuleType __all__ = [ 'get_importer', 'iter_importers', 'get_loader', 'find_loader', 'walk_packages', 'iter_modules', 'ImpImporter', 'ImpLoader', 'read_code', 'extend_path', ] def read_code(stream): # This helper is needed in order for the PEP 302 emulation to # correctly handle compiled files import marshal magic = stream.read(4) if magic != imp.get_magic(): return None stream.read(4) # Skip timestamp return marshal.load(stream) def simplegeneric(func): """Make a trivial single-dispatch generic function""" registry = {} def wrapper(*args, **kw): ob = args[0] try: cls = ob.__class__ except AttributeError: cls = type(ob) try: mro = cls.__mro__ except AttributeError: try: class cls(cls, object): pass mro = cls.__mro__[1:] except TypeError: mro = object, # must be an ExtensionClass or some such :( for t in mro: if t in registry: return registry[t](*args, **kw) else: return func(*args, **kw) try: wrapper.__name__ = func.__name__ except (TypeError, AttributeError): pass # Python 2.3 doesn't allow functions to be renamed def register(typ, func=None): if func is None: return lambda f: register(typ, f) registry[typ] = func return func wrapper.__dict__ = func.__dict__ wrapper.__doc__ = func.__doc__ wrapper.register = register return wrapper def walk_packages(path=None, prefix='', onerror=None): """Yields (module_loader, name, ispkg) for all modules recursively on path, or, if path is None, all accessible modules. 'path' should be either None or a list of paths to look for modules in. 'prefix' is a string to output on the front of every module name on output. Note that this function must import all *packages* (NOT all modules!) on the given path, in order to access the __path__ attribute to find submodules. 'onerror' is a function which gets called with one argument (the name of the package which was being imported) if any exception occurs while trying to import a package. If no onerror function is supplied, ImportErrors are caught and ignored, while all other exceptions are propagated, terminating the search. Examples: # list all modules python can access walk_packages() # list all submodules of ctypes walk_packages(ctypes.__path__, ctypes.__name__+'.') """ def seen(p, m={}): if p in m: return True m[p] = True for importer, name, ispkg in iter_modules(path, prefix): yield importer, name, ispkg if ispkg: try: __import__(name) except ImportError: if onerror is not None: onerror(name) except Exception: if onerror is not None: onerror(name) else: raise else: path = getattr(sys.modules[name], '__path__', None) or [] # don't traverse path items we've seen before path = [p for p in path if not seen(p)] for item in walk_packages(path, name+'.', onerror): yield item def iter_modules(path=None, prefix=''): """Yields (module_loader, name, ispkg) for all submodules on path, or, if path is None, all top-level modules on sys.path. 'path' should be either None or a list of paths to look for modules in. 'prefix' is a string to output on the front of every module name on output. """ if path is None: importers = iter_importers() else: importers = map(get_importer, path) yielded = {} for i in importers: for name, ispkg in iter_importer_modules(i, prefix): if name not in yielded: yielded[name] = 1 yield i, name, ispkg #@simplegeneric def iter_importer_modules(importer, prefix=''): if not hasattr(importer, 'iter_modules'): return [] return importer.iter_modules(prefix) iter_importer_modules = simplegeneric(iter_importer_modules) class ImpImporter: """PEP 302 Importer that wraps Python's "classic" import algorithm ImpImporter(dirname) produces a PEP 302 importer that searches that directory. ImpImporter(None) produces a PEP 302 importer that searches the current sys.path, plus any modules that are frozen or built-in. Note that ImpImporter does not currently support being used by placement on sys.meta_path. """ def __init__(self, path=None): self.path = path def find_module(self, fullname, path=None): # Note: we ignore 'path' argument since it is only used via meta_path subname = fullname.split(".")[-1] if subname != fullname and self.path is None: return None if self.path is None: path = None else: path = [os.path.realpath(self.path)] try: file, filename, etc = imp.find_module(subname, path) except ImportError: return None return ImpLoader(fullname, file, filename, etc) def iter_modules(self, prefix=''): if self.path is None or not os.path.isdir(self.path): return yielded = {} import inspect filenames = os.listdir(self.path) filenames.sort() # handle packages before same-named modules for fn in filenames: modname = inspect.getmodulename(fn) if modname=='__init__' or modname in yielded: continue path = os.path.join(self.path, fn) ispkg = False if not modname and os.path.isdir(path) and '.' not in fn: modname = fn for fn in os.listdir(path): subname = inspect.getmodulename(fn) if subname=='__init__': ispkg = True break else: continue # not a package if modname and '.' not in modname: yielded[modname] = 1 yield prefix + modname, ispkg class ImpLoader: """PEP 302 Loader that wraps Python's "classic" import algorithm """ code = source = None def __init__(self, fullname, file, filename, etc): self.file = file self.filename = filename self.fullname = fullname self.etc = etc def load_module(self, fullname): self._reopen() try: mod = imp.load_module(fullname, self.file, self.filename, self.etc) finally: if self.file: self.file.close() # Note: we don't set __loader__ because we want the module to look # normal; i.e. this is just a wrapper for standard import machinery return mod def get_data(self, pathname): return open(pathname, "rb").read() def _reopen(self): if self.file and self.file.closed: mod_type = self.etc[2] if mod_type==imp.PY_SOURCE: self.file = open(self.filename, 'rU') elif mod_type in (imp.PY_COMPILED, imp.C_EXTENSION): self.file = open(self.filename, 'rb') def _fix_name(self, fullname): if fullname is None: fullname = self.fullname elif fullname != self.fullname: raise ImportError("Loader for module %s cannot handle " "module %s" % (self.fullname, fullname)) return fullname def is_package(self, fullname): fullname = self._fix_name(fullname) return self.etc[2]==imp.PKG_DIRECTORY def get_code(self, fullname=None): fullname = self._fix_name(fullname) if self.code is None: mod_type = self.etc[2] if mod_type==imp.PY_SOURCE: source = self.get_source(fullname) self.code = compile(source, self.filename, 'exec') elif mod_type==imp.PY_COMPILED: self._reopen() try: self.code = read_code(self.file) finally: self.file.close() elif mod_type==imp.PKG_DIRECTORY: self.code = self._get_delegate().get_code() return self.code def get_source(self, fullname=None): fullname = self._fix_name(fullname) if self.source is None: mod_type = self.etc[2] if mod_type==imp.PY_SOURCE: self._reopen() try: self.source = self.file.read() finally: self.file.close() elif mod_type==imp.PY_COMPILED: if os.path.exists(self.filename[:-1]): f = open(self.filename[:-1], 'rU') self.source = f.read() f.close() elif mod_type==imp.PKG_DIRECTORY: self.source = self._get_delegate().get_source() return self.source def _get_delegate(self): return ImpImporter(self.filename).find_module('__init__') def get_filename(self, fullname=None): fullname = self._fix_name(fullname) mod_type = self.etc[2] if self.etc[2]==imp.PKG_DIRECTORY: return self._get_delegate().get_filename() elif self.etc[2] in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION): return self.filename return None try: import zipimport from zipimport import zipimporter def iter_zipimport_modules(importer, prefix=''): dirlist = zipimport._zip_directory_cache[importer.archive].keys() dirlist.sort() _prefix = importer.prefix plen = len(_prefix) yielded = {} import inspect for fn in dirlist: if not fn.startswith(_prefix): continue fn = fn[plen:].split(os.sep) if len(fn)==2 and fn[1].startswith('__init__.py'): if fn[0] not in yielded: yielded[fn[0]] = 1 yield fn[0], True if len(fn)!=1: continue modname = inspect.getmodulename(fn[0]) if modname=='__init__': continue if modname and '.' not in modname and modname not in yielded: yielded[modname] = 1 yield prefix + modname, False iter_importer_modules.register(zipimporter, iter_zipimport_modules) except ImportError: pass def get_importer(path_item): """Retrieve a PEP 302 importer for the given path item The returned importer is cached in sys.path_importer_cache if it was newly created by a path hook. If there is no importer, a wrapper around the basic import machinery is returned. This wrapper is never inserted into the importer cache (None is inserted instead). The cache (or part of it) can be cleared manually if a rescan of sys.path_hooks is necessary. """ try: importer = sys.path_importer_cache[path_item] except KeyError: for path_hook in sys.path_hooks: try: importer = path_hook(path_item) break except ImportError: pass else: importer = None sys.path_importer_cache.setdefault(path_item, importer) if importer is None: try: importer = ImpImporter(path_item) except ImportError: importer = None return importer def iter_importers(fullname=""): """Yield PEP 302 importers for the given module name If fullname contains a '.', the importers will be for the package containing fullname, otherwise they will be importers for sys.meta_path, sys.path, and Python's "classic" import machinery, in that order. If the named module is in a package, that package is imported as a side effect of invoking this function. Non PEP 302 mechanisms (e.g. the Windows registry) used by the standard import machinery to find files in alternative locations are partially supported, but are searched AFTER sys.path. Normally, these locations are searched BEFORE sys.path, preventing sys.path entries from shadowing them. For this to cause a visible difference in behaviour, there must be a module or package name that is accessible via both sys.path and one of the non PEP 302 file system mechanisms. In this case, the emulation will find the former version, while the builtin import mechanism will find the latter. Items of the following types can be affected by this discrepancy: imp.C_EXTENSION, imp.PY_SOURCE, imp.PY_COMPILED, imp.PKG_DIRECTORY """ if fullname.startswith('.'): raise ImportError("Relative module names not supported") if '.' in fullname: # Get the containing package's __path__ pkg = '.'.join(fullname.split('.')[:-1]) if pkg not in sys.modules: __import__(pkg) path = getattr(sys.modules[pkg], '__path__', None) or [] else: for importer in sys.meta_path: yield importer path = sys.path for item in path: yield get_importer(item) if '.' not in fullname: yield ImpImporter() def get_loader(module_or_name): """Get a PEP 302 "loader" object for module_or_name If the module or package is accessible via the normal import mechanism, a wrapper around the relevant part of that machinery is returned. Returns None if the module cannot be found or imported. If the named module is not already imported, its containing package (if any) is imported, in order to establish the package __path__. This function uses iter_importers(), and is thus subject to the same limitations regarding platform-specific special import locations such as the Windows registry. """ if module_or_name in sys.modules: module_or_name = sys.modules[module_or_name] if isinstance(module_or_name, ModuleType): module = module_or_name loader = getattr(module, '__loader__', None) if loader is not None: return loader fullname = module.__name__ else: fullname = module_or_name return find_loader(fullname) def find_loader(fullname): """Find a PEP 302 "loader" object for fullname If fullname contains dots, path must be the containing package's __path__. Returns None if the module cannot be found or imported. This function uses iter_importers(), and is thus subject to the same limitations regarding platform-specific special import locations such as the Windows registry. """ for importer in iter_importers(fullname): loader = importer.find_module(fullname) if loader is not None: return loader return None def extend_path(path, name): """Extend a package's path. Intended use is to place the following code in a package's __init__.py: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) This will add to the package's __path__ all subdirectories of directories on sys.path named after the package. This is useful if one wants to distribute different parts of a single logical package as multiple directories. It also looks for *.pkg files beginning where * matches the name argument. This feature is similar to *.pth files (see site.py), except that it doesn't special-case lines starting with 'import'. A *.pkg file is trusted at face value: apart from checking for duplicates, all entries found in a *.pkg file are added to the path, regardless of whether they are exist the filesystem. (This is a feature.) If the input path is not a list (as is the case for frozen packages) it is returned unchanged. The input path is not modified; an extended copy is returned. Items are only appended to the copy at the end. It is assumed that sys.path is a sequence. Items of sys.path that are not (unicode or 8-bit) strings referring to existing directories are ignored. Unicode items of sys.path that cause errors when used as filenames may cause this function to raise an exception (in line with os.path.isdir() behavior). """ if not isinstance(path, list): # This could happen e.g. when this is called from inside a # frozen package. Return the path unchanged in that case. return path pname = os.path.join(*name.split('.')) # Reconstitute as relative path # Just in case os.extsep != '.' sname = os.extsep.join(name.split('.')) sname_pkg = sname + os.extsep + "pkg" init_py = "__init__" + os.extsep + "py" path = path[:] # Start with a copy of the existing path for dir in sys.path: if not isinstance(dir, basestring) or not os.path.isdir(dir): continue subdir = os.path.join(dir, pname) # XXX This may still add duplicate entries to path on # case-insensitive filesystems initfile = os.path.join(subdir, init_py) if subdir not in path and os.path.isfile(initfile): path.append(subdir) # XXX Is this the right thing for subpackages like zope.app? # It looks for a file named "zope.app.pkg" pkgfile = os.path.join(dir, sname_pkg) if os.path.isfile(pkgfile): try: f = open(pkgfile) except IOError: msg = sys.exc_info()[1] sys.stderr.write("Can't open %s: %s\n" % (pkgfile, msg.args[0])) else: for line in f: line = line.rstrip('\n') if not line or line.startswith('#'): continue path.append(line) # Don't check for existence! f.close() return path def get_data(package, resource): """Get a resource from a package. This is a wrapper round the PEP 302 loader get_data API. The package argument should be the name of a package, in standard module format (foo.bar). The resource argument should be in the form of a relative filename, using '/' as the path separator. The parent directory name '..' is not allowed, and nor is a rooted name (starting with a '/'). The function returns a binary string, which is the contents of the specified resource. For packages located in the filesystem, which have already been imported, this is the rough equivalent of d = os.path.dirname(sys.modules[package].__file__) data = open(os.path.join(d, resource), 'rb').read() If the package cannot be located or loaded, or it uses a PEP 302 loader which does not support get_data(), then None is returned. """ loader = get_loader(package) if loader is None or not hasattr(loader, 'get_data'): return None mod = sys.modules.get(package) or loader.load_module(package) if mod is None or not hasattr(mod, '__file__'): return None # Modify the resource name to be compatible with the loader.get_data # signature - an os.path format "filename" starting with the dirname of # the package's __file__ parts = resource.split('/') parts.insert(0, os.path.dirname(mod.__file__)) resource_name = os.path.join(*parts) return loader.get_data(resource_name) venusian-1.0a8/venusian/__init__.py0000664000175000017500000003561212132726677020216 0ustar chrismchrism00000000000000import imp from inspect import getmembers import sys from venusian.compat import iter_modules from venusian.compat import is_nonstr_iter from venusian.advice import getFrameInfo ATTACH_ATTR = '__venusian_callbacks__' class Scanner(object): def __init__(self, **kw): self.__dict__.update(kw) def scan(self, package, categories=None, onerror=None, ignore=None): """ Scan a Python package and any of its subpackages. All top-level objects will be considered; those marked with venusian callback attributes related to ``category`` will be processed. The ``package`` argument should be a reference to a Python package or module object. The ``categories`` argument should be sequence of Venusian callback categories (each category usually a string) or the special value ``None`` which means all Venusian callback categories. The default is ``None``. The ``onerror`` argument should either be ``None`` or a callback function which behaves the same way as the ``onerror`` callback function described in http://docs.python.org/library/pkgutil.html#pkgutil.walk_packages . By default, during a scan, Venusian will propagate all errors that happen during its code importing process, including :exc:`ImportError`. If you use a custom ``onerror`` callback, you can change this behavior. Here's an example ``onerror`` callback that ignores :exc:`ImportError`:: import sys def onerror(name): if not issubclass(sys.exc_info()[0], ImportError): raise # reraise the last exception The ``name`` passed to ``onerror`` is the module or package dotted name that could not be imported due to an exception. .. versionadded:: 1.0 the ``onerror`` callback The ``ignore`` argument allows you to ignore certain modules, packages, or global objects during a scan. It should be a sequence containing strings and/or callables that will be used to match against the full dotted name of each object encountered during a scan. The sequence can contain any of these three types of objects: - A string representing a full dotted name. To name an object by dotted name, use a string representing the full dotted name. For example, if you want to ignore the ``my.package`` package *and any of its subobjects or subpackages* during the scan, pass ``ignore=['my.package']``. - A string representing a relative dotted name. To name an object relative to the ``package`` passed to this method, use a string beginning with a dot. For example, if the ``package`` you've passed is imported as ``my.package``, and you pass ``ignore=['.mymodule']``, the ``my.package.mymodule`` mymodule *and any of its subobjects or subpackages* will be omitted during scan processing. - A callable that accepts a full dotted name string of an object as its single positional argument and returns ``True`` or ``False``. For example, if you want to skip all packages, modules, and global objects with a full dotted path that ends with the word "tests", you can use ``ignore=[re.compile('tests$').search]``. If the callable returns ``True`` (or anything else truthy), the object is ignored, if it returns ``False`` (or anything else falsy) the object is not ignored. *Note that unlike string matches, ignores that use a callable don't cause submodules and subobjects of a module or package represented by a dotted name to also be ignored, they match individual objects found during a scan, including packages, modules, and global objects*. You can mix and match the three types of strings in the list. For example, if the package being scanned is ``my``, ``ignore=['my.package', '.someothermodule', re.compile('tests$').search]`` would cause ``my.package`` (and all its submodules and subobjects) to be ignored, ``my.someothermodule`` to be ignored, and any modules, packages, or global objects found during the scan that have a full dotted name that ends with the word ``tests`` to be ignored. Note that packages and modules matched by any ignore in the list will not be imported, and their top-level code will not be run as a result. A string or callable alone can also be passed as ``ignore`` without a surrounding list. .. versionadded:: 1.0a3 the ``ignore`` argument """ pkg_name = package.__name__ if ignore is not None and not is_nonstr_iter(ignore): ignore = [ignore] def _ignore(fullname): if ignore is not None: for ign in ignore: if isinstance(ign, str): if ign.startswith('.'): # leading dotted name relative to scanned package if fullname.startswith(pkg_name + ign): return True else: # non-leading-dotted name absolute object name if fullname.startswith(ign): return True else: # function if ign(fullname): return True return False def invoke(mod_name, name, ob): fullname = mod_name + '.' + name if _ignore(fullname): return category_keys = categories try: # Some metaclasses do insane things when asked for an # ``ATTACH_ATTR``, like not raising an AttributeError but # some other arbitary exception. Some even shittier # introspected code lets us access ``ATTACH_ATTR`` far but # barfs on a second attribute access for ``attached_to`` # (still not raising an AttributeError, but some other # arbitrary exception). Finally, the shittiest code of all # allows the attribute access of the ``ATTACH_ATTR`` *and* # ``attached_to``, (say, both ``ob.__getattr__`` and # ``attached_categories.__getattr__`` returning a proxy for # any attribute access), which either a) isn't callable or b) # is callable, but, when called, shits its pants in an # potentially arbitrary way (although for b, only TypeError # has been seen in the wild, from PyMongo). Thus the # catchall except: return here, which in any other case would # be high treason. attached_categories = getattr(ob, ATTACH_ATTR) if not attached_categories.attached_to(ob): return except: return if category_keys is None: category_keys = list(attached_categories.keys()) category_keys.sort() for category in category_keys: callbacks = attached_categories.get(category, []) for callback, callback_mod_name in callbacks: if callback_mod_name != mod_name: # avoid processing objects that were imported into this # module but were not actually defined there continue callback(self, name, ob) for name, ob in getmembers(package): # whether it's a module or a package, we need to scan its # members; walk_packages only iterates over submodules and # subpackages invoke(pkg_name, name, ob) if hasattr(package, '__path__'): # package, not module results = walk_packages(package.__path__, package.__name__+'.', onerror=onerror, ignore=_ignore) for importer, modname, ispkg in results: loader = importer.find_module(modname) if loader is not None: # happens on pypy with orphaned pyc try: if hasattr(loader, 'etc'): # python < py3.3 module_type = loader.etc[2] else: # pragma: no cover # py3.3b2+ (importlib-using) module_type = imp.PY_SOURCE fn = loader.get_filename() if fn.endswith(('.pyc', '.pyo', '$py.class')): module_type = imp.PY_COMPILED # only scrape members from non-orphaned source files # and package directories if module_type in (imp.PY_SOURCE, imp.PKG_DIRECTORY): # NB: use __import__(modname) rather than # loader.load_module(modname) to prevent # inappropriate double-execution of module code try: __import__(modname) except Exception: if onerror is not None: onerror(modname) else: raise module = sys.modules.get(modname) if module is not None: for name, ob in getmembers(module, None): invoke(modname, name, ob) finally: if ( hasattr(loader, 'file') and hasattr(loader.file,'close') ): loader.file.close() class AttachInfo(object): """ An instance of this class is returned by the :func:`venusian.attach` function. It has the following attributes: ``scope`` One of ``exec``, ``module``, ``class``, ``function call`` or ``unknown`` (each a string). This is the scope detected while executing the decorator which runs the attach function. ``module`` The module in which the decorated function was defined. ``locals`` A dictionary containing decorator frame's f_locals. ``globals`` A dictionary containing decorator frame's f_globals. ``category`` The ``category`` argument passed to ``attach`` (or ``None``, the default). ``codeinfo`` A tuple in the form ``(filename, lineno, function, sourceline)`` representing the context of the venusian decorator used. Eg. ``('/home/chrism/projects/venusian/tests/test_advice.py', 81, 'testCallInfo', 'add_handler(foo, bar)')`` """ def __init__(self, **kw): self.__dict__.update(kw) class Categories(dict): def __init__(self, attached_to): super(dict, self).__init__() if attached_to is None: self.attached_id = None else: self.attached_id = id(attached_to) def attached_to(self, obj): if self.attached_id: return self.attached_id == id(obj) return True def attach(wrapped, callback, category=None, depth=1): """ Attach a callback to the wrapped object. It will be found later during a scan. This function returns an instance of the :class:`venusian.AttachInfo` class.""" frame = sys._getframe(depth+1) scope, module, f_locals, f_globals, codeinfo = getFrameInfo(frame) module_name = getattr(module, '__name__', None) if scope == 'class': # we're in the midst of a class statement categories = f_locals.setdefault(ATTACH_ATTR, Categories(None)) callbacks = categories.setdefault(category, []) callbacks.append((callback, module_name)) else: categories = getattr(wrapped, ATTACH_ATTR, None) if categories is None or not categories.attached_to(wrapped): # if there aren't any attached categories, or we've retrieved # some by inheritance, we need to create new ones categories = Categories(wrapped) setattr(wrapped, ATTACH_ATTR, categories) callbacks = categories.setdefault(category, []) callbacks.append((callback, module_name)) return AttachInfo( scope=scope, module=module, locals=f_locals, globals=f_globals, category=category, codeinfo=codeinfo) def walk_packages(path=None, prefix='', onerror=None, ignore=None): """Yields (module_loader, name, ispkg) for all modules recursively on path, or, if path is None, all accessible modules. 'path' should be either None or a list of paths to look for modules in. 'prefix' is a string to output on the front of every module name on output. Note that this function must import all *packages* (NOT all modules!) on the given path, in order to access the __path__ attribute to find submodules. 'onerror' is a function which gets called with one argument (the name of the package which was being imported) if any exception occurs while trying to import a package. If no onerror function is supplied, any exception is exceptions propagated, terminating the search. 'ignore' is a function fed a fullly dotted name; if it returns True, the object is skipped and not returned in results (and if it's a package it's not imported). Examples: # list all modules python can access walk_packages() # list all submodules of ctypes walk_packages(ctypes.__path__, ctypes.__name__+'.') # NB: we can't just use pkgutils.walk_packages because we need to ignore # things """ def seen(p, m={}): if p in m: # pragma: no cover return True m[p] = True # iter_modules is nonrecursive for importer, name, ispkg in iter_modules(path, prefix): if ignore is not None and ignore(name): # if name is a package, ignoring here will cause # all subpackages and submodules to be ignored too continue # do any onerror handling before yielding if ispkg: try: __import__(name) except Exception: if onerror is not None: onerror(name) else: raise else: yield importer, name, ispkg path = getattr(sys.modules[name], '__path__', None) or [] # don't traverse path items we've seen before path = [p for p in path if not seen(p)] for item in walk_packages(path, name+'.', onerror, ignore): yield item else: yield importer, name, ispkg venusian-1.0a8/venusian/tests/0000775000175000017500000000000012132727372017231 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/0000775000175000017500000000000012132727372021102 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/nested/0000775000175000017500000000000012132727372022364 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/nested/sub1/0000775000175000017500000000000012132727372023236 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/nested/sub1/__init__.py0000644000175000017500000000020611714637044025344 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/nested/sub1/subsub1/0000775000175000017500000000000012132727372024622 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/nested/sub1/subsub1/__init__.py0000644000175000017500000000020611714637044026730 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/nested/__init__.py0000644000175000017500000000020711714637044024473 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/nested/sub2/0000775000175000017500000000000012132727372023237 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/nested/sub2/subsub2/0000775000175000017500000000000012132727372024624 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/nested/sub2/subsub2/__init__.py0000644000175000017500000000020611714637044026732 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/nested/sub2/__init__.py0000644000175000017500000000020611714637044025345 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/importerror/0000775000175000017500000000000012132727372023466 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/importerror/__init__.py0000644000175000017500000000020711626106362025571 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/importerror/will_cause_import_error.py0000664000175000017500000000002412015221272030752 0ustar chrismchrism00000000000000import doesnt.exist venusian-1.0a8/venusian/tests/fixtures/attrerror/0000775000175000017500000000000012132727372023126 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/attrerror/__init__.py0000644000175000017500000000020711626111341025222 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/attrerror/will_cause_import_error.py0000664000175000017500000000002612015221272030414 0ustar chrismchrism00000000000000raise AttributeError venusian-1.0a8/venusian/tests/fixtures/category.py0000644000175000017500000000045711526650777023307 0ustar chrismchrism00000000000000from venusian.tests.fixtures import categorydecorator from venusian.tests.fixtures import categorydecorator2 @categorydecorator(function=True) def function(request): # pragma: no cover return request @categorydecorator2(function=True) def function2(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/subpackages/0000775000175000017500000000000012132727372023372 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/subpackages/mod2.py0000644000175000017500000000002611626111536024575 0ustar chrismchrism00000000000000raise AttributeError venusian-1.0a8/venusian/tests/fixtures/subpackages/__init__.py0000644000175000017500000000020711626106362025475 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/subpackages/childpackage/0000775000175000017500000000000012132727372025771 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/subpackages/childpackage/__init__.py0000644000175000017500000000000011626106362030063 0ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/subpackages/childpackage/will_cause_import_error.py0000664000175000017500000000002412015221272033255 0ustar chrismchrism00000000000000import doesnt.exist venusian-1.0a8/venusian/tests/fixtures/one/0000775000175000017500000000000012132727372021663 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/one/module2.py0000644000175000017500000000064311526650777023617 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request class Class(object): @decorator(method=True) def method(self, request): # pragma: no cover return request class Instance(object): def __call__(self, request): # pragma: no cover return request inst = Instance() inst = decorator(instance=True)(inst) venusian-1.0a8/venusian/tests/fixtures/one/__init__.py0000644000175000017500000000001211526650777023775 0ustar chrismchrism00000000000000# package venusian-1.0a8/venusian/tests/fixtures/one/module.py0000644000175000017500000000064311526650777023535 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request class Class(object): @decorator(method=True) def method(self, request): # pragma: no cover return request class Instance(object): def __call__(self, request): # pragma: no cover return request inst = Instance() inst = decorator(instance=True)(inst) venusian-1.0a8/venusian/tests/fixtures/inheritance.py0000644000175000017500000000017211540146774023746 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator() class Parent(object): pass class Child(Parent): pass venusian-1.0a8/venusian/tests/fixtures/two/0000775000175000017500000000000012132727372021713 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/two/mod2.py0000644000175000017500000000003011540146774023120 0ustar chrismchrism00000000000000from .mod1 import Class venusian-1.0a8/venusian/tests/fixtures/two/__init__.py0000644000175000017500000000000011540146774024013 0ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/two/mod1.py0000644000175000017500000000013211540146774023122 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator() class Class(object): pass venusian-1.0a8/venusian/tests/fixtures/__init__.py0000644000175000017500000000133211526650777023222 0ustar chrismchrism00000000000000import venusian class decorator(object): category = None def __init__(self, **kw): self.__dict__.update(kw) def __call__(self, wrapped): view_config = self.__dict__.copy() def callback(context, name, ob): context.test(ob=ob, name=name, **view_config) info = venusian.attach(wrapped, callback, category=self.category) if info.scope == 'class': # we're in the midst of a class statement if view_config.get('attr') is None: view_config['attr'] = wrapped.__name__ return wrapped class categorydecorator(decorator): category = 'mycategory' class categorydecorator2(decorator): category = 'mycategory2' venusian-1.0a8/venusian/tests/fixtures/import_and_scan/0000775000175000017500000000000012132727372024242 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/import_and_scan/one.py0000664000175000017500000000037611745305325025402 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator from venusian.tests.fixtures.import_and_scan.two import twofunction # should not be scanned @decorator(function=True) def onefunction(request): # pragma: no cover twofunction(request) return request venusian-1.0a8/venusian/tests/fixtures/import_and_scan/two.py0000664000175000017500000000021111745304651025417 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def twofunction(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/import_and_scan/__init__.py0000664000175000017500000000001211745304651026344 0ustar chrismchrism00000000000000# package venusian-1.0a8/venusian/tests/fixtures/attrerror_package/0000775000175000017500000000000012132727372024601 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/attrerror_package/__init__.py0000644000175000017500000000020711714637044026710 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/attrerror_package/will_cause_import_error/0000775000175000017500000000000012132727372031533 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/attrerror_package/will_cause_import_error/__init__.py0000664000175000017500000000002612015221272033626 0ustar chrismchrism00000000000000raise AttributeError venusian-1.0a8/venusian/tests/fixtures/pyc/0000775000175000017500000000000012132727372021675 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/pyc/subpackage/0000775000175000017500000000000012132727372024002 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/pyc/subpackage/__init__.py0000644000175000017500000000021211526650777026116 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def pkgfunction(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/pyc/__init__.py0000644000175000017500000000000711526650777024013 0ustar chrismchrism00000000000000# pkg venusian-1.0a8/venusian/tests/fixtures/pyc/module.py0000644000175000017500000000064311526650777023547 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request class Class(object): @decorator(method=True) def method(self, request): # pragma: no cover return request class Instance(object): def __call__(self, request): # pragma: no cover return request inst = Instance() inst = decorator(instance=True)(inst) venusian-1.0a8/venusian/tests/fixtures/classdecorator.py0000644000175000017500000000025711526650777024500 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(superclass=True) class SuperClass(object): pass @decorator(subclass=True) class SubClass(SuperClass): pass venusian-1.0a8/venusian/tests/fixtures/importerror_package/0000775000175000017500000000000012132727372025141 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/importerror_package/__init__.py0000644000175000017500000000020711714637044027250 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/importerror_package/will_cause_import_error/0000775000175000017500000000000012132727372032073 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/importerror_package/will_cause_import_error/__init__.py0000664000175000017500000000002412015221272034164 0ustar chrismchrism00000000000000import doesnt.exist venusian-1.0a8/venusian/tests/fixtures/importonly/0000775000175000017500000000000012132727372023316 5ustar chrismchrism00000000000000venusian-1.0a8/venusian/tests/fixtures/importonly/one.py0000644000175000017500000000034111743006275024444 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator from venusian.tests.fixtures.importonly.two import twofunction # should not be scanned @decorator(function=True) def onefunction(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/importonly/two.py0000644000175000017500000000021111743006275024470 0ustar chrismchrism00000000000000from venusian.tests.fixtures import decorator @decorator(function=True) def twofunction(request): # pragma: no cover return request venusian-1.0a8/venusian/tests/fixtures/importonly/__init__.py0000644000175000017500000000001211743006275025415 0ustar chrismchrism00000000000000# package venusian-1.0a8/venusian/tests/test_advice.py0000644000175000017500000000612111622562555022076 0ustar chrismchrism00000000000000############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for advice This module was adapted from 'protocols.tests.advice', part of the Python Enterprise Application Kit (PEAK). Please notify the PEAK authors (pje@telecommunity.com and tsarna@sarna.org) if bugs are found or Zope-specific changes are required, so that the PEAK version of this module can be kept in sync. PEAK is a Python application framework that interoperates with (but does not require) Zope 3 and Twisted. It provides tools for manipulating UML models, object-relational persistence, aspect-oriented programming, and more. Visit the PEAK home page at http://peak.telecommunity.com for more information. $Id: test_advice.py 40836 2005-12-16 22:40:51Z benji_york $ """ import unittest import sys from venusian import advice PY3 = sys.version_info[0] >= 3 if not PY3: class ClassicClass: classLevelFrameInfo = advice.getFrameInfo(sys._getframe()) class NewStyleClass(object): classLevelFrameInfo = advice.getFrameInfo(sys._getframe()) moduleLevelFrameInfo = advice.getFrameInfo(sys._getframe()) class FrameInfoTest(unittest.TestCase): classLevelFrameInfo = advice.getFrameInfo(sys._getframe()) def testModuleInfo(self): kind, module, f_locals, f_globals, codeinfo = moduleLevelFrameInfo self.assertEquals(kind, "module") for d in module.__dict__, f_locals, f_globals: self.assert_(d is globals()) self.assertEqual(len(codeinfo), 4) if not PY3: def testClassicClassInfo(self): (kind, module, f_locals, f_globals, codeinfo) = ClassicClass.classLevelFrameInfo self.assertEquals(kind, "class") self.assert_(f_locals is ClassicClass.__dict__) # ??? for d in module.__dict__, f_globals: self.assert_(d is globals()) self.assertEqual(len(codeinfo), 4) def testNewStyleClassInfo(self): (kind, module, f_locals, f_globals, codeinfo) = NewStyleClass.classLevelFrameInfo self.assertEquals(kind, "class") for d in module.__dict__, f_globals: self.assert_(d is globals()) self.assertEqual(len(codeinfo), 4) def testCallInfo(self): (kind, module, f_locals, f_globals, codeinfo) = advice.getFrameInfo(sys._getframe()) self.assertEquals(kind, "function call") self.assert_(f_locals is locals()) # ??? for d in module.__dict__, f_globals: self.assert_(d is globals()) self.assertEqual(len(codeinfo), 4) venusian-1.0a8/venusian/tests/__init__.py0000644000175000017500000000001211526650777021343 0ustar chrismchrism00000000000000# package venusian-1.0a8/venusian/tests/test_venusian.py0000664000175000017500000006456112015221272022472 0ustar chrismchrism00000000000000import unittest import sys import re class Test(object): def __init__(self): self.registrations = [] def __call__(self, **kw): self.registrations.append(kw) class TestScanner(unittest.TestCase): def _makeOne(self, **kw): from venusian import Scanner return Scanner(**kw) def test_package(self): from venusian.tests.fixtures import one test = Test() scanner = self._makeOne(test=test) scanner.scan(one) self.assertEqual(len(test.registrations), 6) test.registrations.sort(key=lambda x: (x['name'], x['ob'].__module__)) from venusian.tests.fixtures.one.module import function as func1 from venusian.tests.fixtures.one.module2 import function as func2 from venusian.tests.fixtures.one.module import inst as inst1 from venusian.tests.fixtures.one.module2 import inst as inst2 from venusian.tests.fixtures.one.module import Class as Class1 from venusian.tests.fixtures.one.module2 import Class as Class2 self.assertEqual(test.registrations[0]['name'], 'Class') self.assertEqual(test.registrations[0]['ob'], Class1) self.assertEqual(test.registrations[0]['method'], True) self.assertEqual(test.registrations[1]['name'], 'Class') self.assertEqual(test.registrations[1]['ob'], Class2) self.assertEqual(test.registrations[1]['method'], True) self.assertEqual(test.registrations[2]['name'], 'function') self.assertEqual(test.registrations[2]['ob'], func1) self.assertEqual(test.registrations[2]['function'], True) self.assertEqual(test.registrations[3]['name'], 'function') self.assertEqual(test.registrations[3]['ob'], func2) self.assertEqual(test.registrations[3]['function'], True) self.assertEqual(test.registrations[4]['name'], 'inst') self.assertEqual(test.registrations[4]['ob'], inst1) self.assertEqual(test.registrations[4]['instance'], True) self.assertEqual(test.registrations[5]['name'], 'inst') self.assertEqual(test.registrations[5]['ob'], inst2) self.assertEqual(test.registrations[5]['instance'], True) def test_package_with_orphaned_pyc_file(self): # There is a module2.pyc file in the "pycfixtures" package; it # has no corresponding .py source file. Such orphaned .pyc # files should be ignored during scanning. from venusian.tests.fixtures import pyc test = Test() scanner = self._makeOne(test=test) scanner.scan(pyc) self.assertEqual(len(test.registrations), 4) test.registrations.sort(key=lambda x: (x['name'], x['ob'].__module__)) from venusian.tests.fixtures.pyc.module import function as func1 from venusian.tests.fixtures.pyc.module import inst as inst1 from venusian.tests.fixtures.pyc.module import Class as Class1 from venusian.tests.fixtures.pyc import subpackage self.assertEqual(test.registrations[0]['name'], 'Class') self.assertEqual(test.registrations[0]['ob'], Class1) self.assertEqual(test.registrations[0]['method'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func1) self.assertEqual(test.registrations[1]['function'], True) self.assertEqual(test.registrations[2]['name'], 'inst') self.assertEqual(test.registrations[2]['ob'], inst1) self.assertEqual(test.registrations[2]['instance'], True) self.assertEqual(test.registrations[3]['name'], 'pkgfunction') self.assertEqual(test.registrations[3]['ob'], subpackage.pkgfunction) self.assertEqual(test.registrations[3]['function'], True) def test_module(self): from venusian.tests.fixtures.one import module test = Test() scanner = self._makeOne(test=test) scanner.scan(module) self.assertEqual(len(test.registrations), 3) test.registrations.sort(key=lambda x: (x['name'], x['ob'].__module__)) from venusian.tests.fixtures.one.module import function as func1 from venusian.tests.fixtures.one.module import inst as inst1 from venusian.tests.fixtures.one.module import Class as Class1 self.assertEqual(test.registrations[0]['name'], 'Class') self.assertEqual(test.registrations[0]['ob'], Class1) self.assertEqual(test.registrations[0]['method'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func1) self.assertEqual(test.registrations[1]['function'], True) self.assertEqual(test.registrations[2]['name'], 'inst') self.assertEqual(test.registrations[2]['ob'], inst1) self.assertEqual(test.registrations[2]['instance'], True) def test_ignore_imported(self): # even though "twofunction" is imported into "one", it should not # be registered, because it's only imported in one and not defined # there from venusian.tests.fixtures.importonly import one from venusian.tests.fixtures.importonly import two test = Test() scanner = self._makeOne(test=test) scanner.scan(one) self.assertEqual(len(test.registrations), 1) scanner.scan(two) self.assertEqual(len(test.registrations), 2) def test_dont_ignore_legit_decorators(self): # make sure venusian picks up other decorated things from # imported modules when the whole package is scanned from venusian.tests.fixtures import import_and_scan test = Test() scanner = self._makeOne(test=test) scanner.scan(import_and_scan) self.assertEqual(len(test.registrations), 2) def test_one_category(self): from venusian.tests.fixtures import category test = Test() scanner = self._makeOne(test=test) scanner.scan(category, categories=('mycategory',)) self.assertEqual(len(test.registrations), 1) self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], category.function) self.assertEqual(test.registrations[0]['function'], True) def test_all_categories_implicit(self): from venusian.tests.fixtures import category test = Test() scanner = self._makeOne(test=test) scanner.scan(category) self.assertEqual(len(test.registrations), 2) self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], category.function) self.assertEqual(test.registrations[0]['function'], True) self.assertEqual(test.registrations[1]['name'], 'function2') self.assertEqual(test.registrations[1]['ob'], category.function2) self.assertEqual(test.registrations[1]['function'], True) def test_all_categories_explicit(self): from venusian.tests.fixtures import category test = Test() scanner = self._makeOne(test=test) scanner.scan(category, categories=('mycategory', 'mycategory2')) self.assertEqual(len(test.registrations), 2) self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], category.function) self.assertEqual(test.registrations[0]['function'], True) self.assertEqual(test.registrations[1]['name'], 'function2') self.assertEqual(test.registrations[1]['ob'], category.function2) self.assertEqual(test.registrations[1]['function'], True) if sys.version_info >= (2, 6): def test_decorations_arent_inherited(self): from venusian.tests.fixtures import inheritance test = Test() scanner = self._makeOne(test=test) scanner.scan(inheritance) self.assertEqual(test.registrations, [ dict(name='Parent', ob=inheritance.Parent), ]) def test_classdecorator(self): # pragma: no cover from venusian.tests.fixtures import classdecorator test = Test() scanner = self._makeOne(test=test) scanner.scan(classdecorator) test.registrations.sort(key=lambda x: (x['name'], x['ob'].__module__)) self.assertEqual(len(test.registrations), 2) self.assertEqual(test.registrations[0]['name'], 'SubClass') self.assertEqual(test.registrations[0]['ob'], classdecorator.SubClass) self.assertEqual(test.registrations[0]['subclass'], True) self.assertEqual(test.registrations[1]['name'], 'SuperClass') self.assertEqual(test.registrations[1]['ob'], classdecorator.SuperClass) self.assertEqual(test.registrations[1]['superclass'], True) def test_scan_only_finds_classdecoration_once(self): from venusian.tests.fixtures import two from venusian.tests.fixtures.two.mod1 import Class test = Test() scanner = self._makeOne(test=test) scanner.scan(two) self.assertEqual(test.registrations, [ dict(name='Class', ob=Class), ]) def test_importerror_during_scan_default_onerror(self): from venusian.tests.fixtures import importerror test = Test() scanner = self._makeOne(test=test) # without a custom onerror, scan will propagate the importerror from # will_cause_import_error self.assertRaises(ImportError, scanner.scan, importerror) def test_importerror_during_scan_default_onerror_with_ignore(self): from venusian.tests.fixtures import importerror test = Test() scanner = self._makeOne(test=test) # scan will ignore the errors from will_cause_import_error due # to us choosing to ignore that package scanner.scan( importerror, ignore='venusian.tests.fixtures.importerror.will_cause_import_error') def test_importerror_during_scan_custom_onerror(self): from venusian.tests.fixtures import importerror test = Test() scanner = self._makeOne(test=test) # with this custom onerror, scan will not propagate the importerror # from will_raise_importerror def onerror(name): if not issubclass(sys.exc_info()[0], ImportError): raise scanner.scan(importerror, onerror=onerror) self.assertEqual(len(test.registrations), 1) from venusian.tests.fixtures.importerror import function as func1 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) def test_importerror_in_package_during_scan_custom_onerror(self): from venusian.tests.fixtures import importerror_package md('venusian.tests.fixtures.importerror_package.will_cause_import_error') test = Test() scanner = self._makeOne(test=test) # with this custom onerror, scan will not propagate the importerror # from will_raise_importerror def onerror(name): raise ValueError self.assertRaises(ValueError, scanner.scan, importerror_package, onerror=onerror) self.assertEqual(len(test.registrations), 1) from venusian.tests.fixtures.importerror_package import function as func1 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) def test_attrerror_during_scan_custom_onerror(self): from venusian.tests.fixtures import attrerror test = Test() scanner = self._makeOne(test=test) # with this custom onerror, scan will not propagate the importerror # from will_raise_importerror def onerror(name): if not issubclass(sys.exc_info()[0], ImportError): raise self.assertRaises(AttributeError, scanner.scan, attrerror, onerror=onerror) self.assertEqual(len(test.registrations), 1) from venusian.tests.fixtures.attrerror import function as func1 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) def test_attrerror_in_package_during_scan_custom_onerror(self): from venusian.tests.fixtures import attrerror_package md('venusian.tests.fixtures.attrerror_package.will_cause_import_error') test = Test() scanner = self._makeOne(test=test) # with this custom onerror, scan will not propagate the importerror # from will_raise_importerror def onerror(name): if not issubclass(sys.exc_info()[0], ImportError): raise self.assertRaises(AttributeError, scanner.scan, attrerror_package, onerror=onerror) self.assertEqual(len(test.registrations), 1) from venusian.tests.fixtures.attrerror_package import function as func1 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) def test_attrerror_in_package_during_scan_no_custom_onerror(self): from venusian.tests.fixtures import attrerror_package md('venusian.tests.fixtures.attrerror_package.will_cause_import_error') test = Test() scanner = self._makeOne(test=test) self.assertRaises(AttributeError, scanner.scan, attrerror_package) self.assertEqual(len(test.registrations), 1) from venusian.tests.fixtures.attrerror_package import function as func1 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) def test_onerror_used_to_swallow_all_exceptions(self): from venusian.tests.fixtures import subpackages test = Test() scanner = self._makeOne(test=test) # onerror can also be used to skip errors while scanning submodules # e.g.: test modules under a given library swallowed = [] def ignore_child(name): swallowed.append(name) scanner.scan(subpackages, onerror=ignore_child) self.assertEqual(swallowed, ['venusian.tests.fixtures.subpackages.childpackage.will_cause_import_error', 'venusian.tests.fixtures.subpackages.mod2']) self.assertEqual(len(test.registrations), 1) from venusian.tests.fixtures.subpackages import function as func1 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) def test_ignore_by_full_dotted_name(self): from venusian.tests.fixtures import one test = Test() scanner = self._makeOne(test=test) scanner.scan( one, ignore=['venusian.tests.fixtures.one.module2'] ) self.assertEqual(len(test.registrations), 3) from venusian.tests.fixtures.one.module import function as func1 from venusian.tests.fixtures.one.module import inst as inst1 from venusian.tests.fixtures.one.module import Class as Class1 self.assertEqual(test.registrations[0]['name'], 'Class') self.assertEqual(test.registrations[0]['ob'], Class1) self.assertEqual(test.registrations[0]['method'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func1) self.assertEqual(test.registrations[1]['function'], True) self.assertEqual(test.registrations[2]['name'], 'inst') self.assertEqual(test.registrations[2]['ob'], inst1) self.assertEqual(test.registrations[2]['instance'], True) def test_ignore_by_full_dotted_name2(self): from venusian.tests.fixtures import nested test = Test() scanner = self._makeOne(test=test) scanner.scan( nested, ignore=['venusian.tests.fixtures.nested.sub1'] ) self.assertEqual(len(test.registrations), 3) from venusian.tests.fixtures.nested import function as func1 from venusian.tests.fixtures.nested.sub2 import function as func2 from venusian.tests.fixtures.nested.sub2.subsub2 import function as func3 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func2) self.assertEqual(test.registrations[1]['function'], True) self.assertEqual(test.registrations[2]['name'], 'function') self.assertEqual(test.registrations[2]['ob'], func3) self.assertEqual(test.registrations[2]['function'], True) def test_ignore_by_full_dotted_name3(self): from venusian.tests.fixtures import nested test = Test() scanner = self._makeOne(test=test) scanner.scan( nested, ignore=['venusian.tests.fixtures.nested.sub1', 'venusian.tests.fixtures.nested.sub2'] ) self.assertEqual(len(test.registrations), 1) from venusian.tests.fixtures.nested import function as func1 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) def test_ignore_by_full_dotted_name4(self): from venusian.tests.fixtures import nested test = Test() scanner = self._makeOne(test=test) scanner.scan( nested, ignore=['venusian.tests.fixtures.nested.sub1', 'venusian.tests.fixtures.nested.function'] ) self.assertEqual(len(test.registrations), 2) from venusian.tests.fixtures.nested.sub2 import function as func2 from venusian.tests.fixtures.nested.sub2.subsub2 import function as func3 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func2) self.assertEqual(test.registrations[0]['function'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func3) self.assertEqual(test.registrations[1]['function'], True) def test_ignore_by_relative_dotted_name(self): from venusian.tests.fixtures import one test = Test() scanner = self._makeOne(test=test) scanner.scan(one, ignore=['.module2']) self.assertEqual(len(test.registrations), 3) from venusian.tests.fixtures.one.module import function as func1 from venusian.tests.fixtures.one.module import inst as inst1 from venusian.tests.fixtures.one.module import Class as Class1 self.assertEqual(test.registrations[0]['name'], 'Class') self.assertEqual(test.registrations[0]['ob'], Class1) self.assertEqual(test.registrations[0]['method'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func1) self.assertEqual(test.registrations[1]['function'], True) self.assertEqual(test.registrations[2]['name'], 'inst') self.assertEqual(test.registrations[2]['ob'], inst1) self.assertEqual(test.registrations[2]['instance'], True) def test_ignore_by_relative_dotted_name2(self): from venusian.tests.fixtures import nested test = Test() scanner = self._makeOne(test=test) scanner.scan( nested, ignore=['.sub1'] ) self.assertEqual(len(test.registrations), 3) from venusian.tests.fixtures.nested import function as func1 from venusian.tests.fixtures.nested.sub2 import function as func2 from venusian.tests.fixtures.nested.sub2.subsub2 import function as func3 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func2) self.assertEqual(test.registrations[1]['function'], True) self.assertEqual(test.registrations[2]['name'], 'function') self.assertEqual(test.registrations[2]['ob'], func3) self.assertEqual(test.registrations[2]['function'], True) def test_ignore_by_relative_dotted_name3(self): from venusian.tests.fixtures import nested test = Test() scanner = self._makeOne(test=test) scanner.scan( nested, ignore=['.sub1', '.sub2'] ) self.assertEqual(len(test.registrations), 1) from venusian.tests.fixtures.nested import function as func1 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) def test_ignore_by_relative_dotted_name4(self): from venusian.tests.fixtures import nested test = Test() scanner = self._makeOne(test=test) scanner.scan( nested, ignore=['.sub1', '.function'] ) self.assertEqual(len(test.registrations), 2) from venusian.tests.fixtures.nested.sub2 import function as func2 from venusian.tests.fixtures.nested.sub2.subsub2 import function as func3 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func2) self.assertEqual(test.registrations[0]['function'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func3) self.assertEqual(test.registrations[1]['function'], True) def test_ignore_by_function(self): from venusian.tests.fixtures import one test = Test() scanner = self._makeOne(test=test) scanner.scan(one, ignore=[re.compile('Class').search, re.compile('inst').search]) self.assertEqual(len(test.registrations), 2) from venusian.tests.fixtures.one.module import function as func1 from venusian.tests.fixtures.one.module2 import function as func2 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func1) self.assertEqual(test.registrations[0]['function'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func2) self.assertEqual(test.registrations[1]['function'], True) def test_ignore_by_function_nested(self): from venusian.tests.fixtures import nested test = Test() scanner = self._makeOne(test=test) scanner.scan( nested, ignore=[re.compile('.function$').search] ) self.assertEqual(len(test.registrations), 0) def test_ignore_by_function_nested2(self): from venusian.tests.fixtures import nested test = Test() scanner = self._makeOne(test=test) scanner.scan( nested, ignore=[re.compile('sub2$').search, re.compile('nested.function$').search] ) self.assertEqual(len(test.registrations), 2) from venusian.tests.fixtures.nested.sub1 import function as func2 from venusian.tests.fixtures.nested.sub1.subsub1 import function as func3 self.assertEqual(test.registrations[0]['name'], 'function') self.assertEqual(test.registrations[0]['ob'], func2) self.assertEqual(test.registrations[0]['function'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func3) self.assertEqual(test.registrations[1]['function'], True) def test_ignore_as_string(self): from venusian.tests.fixtures import one test = Test() scanner = self._makeOne(test=test) scanner.scan(one, ignore='venusian.tests.fixtures.one.module2') self.assertEqual(len(test.registrations), 3) from venusian.tests.fixtures.one.module import function as func1 from venusian.tests.fixtures.one.module import inst as inst1 from venusian.tests.fixtures.one.module import Class as Class1 self.assertEqual(test.registrations[0]['name'], 'Class') self.assertEqual(test.registrations[0]['ob'], Class1) self.assertEqual(test.registrations[0]['method'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func1) self.assertEqual(test.registrations[1]['function'], True) self.assertEqual(test.registrations[2]['name'], 'inst') self.assertEqual(test.registrations[2]['ob'], inst1) self.assertEqual(test.registrations[2]['instance'], True) def test_ignore_mixed_string_and_func(self): import re from venusian.tests.fixtures import one test = Test() scanner = self._makeOne(test=test) scanner.scan(one, ignore=['venusian.tests.fixtures.one.module2', re.compile('inst').search]) self.assertEqual(len(test.registrations), 2) from venusian.tests.fixtures.one.module import function as func1 from venusian.tests.fixtures.one.module import Class as Class1 self.assertEqual(test.registrations[0]['name'], 'Class') self.assertEqual(test.registrations[0]['ob'], Class1) self.assertEqual(test.registrations[0]['method'], True) self.assertEqual(test.registrations[1]['name'], 'function') self.assertEqual(test.registrations[1]['ob'], func1) self.assertEqual(test.registrations[1]['function'], True) def md(name): # pragma: no cover if name in sys.modules: del sys.modules[name] venusian-1.0a8/venusian/advice.py0000644000175000017500000000534311622562555017702 0ustar chrismchrism00000000000000############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Class advice. This module was adapted from 'protocols.advice', part of the Python Enterprise Application Kit (PEAK). Please notify the PEAK authors (pje@telecommunity.com and tsarna@sarna.org) if bugs are found or Zope-specific changes are required, so that the PEAK version of this module can be kept in sync. PEAK is a Python application framework that interoperates with (but does not require) Zope 3 and Twisted. It provides tools for manipulating UML models, object-relational persistence, aspect-oriented programming, and more. Visit the PEAK home page at http://peak.telecommunity.com for more information. $Id: advice.py 25177 2004-06-02 13:17:31Z jim $ """ import inspect import sys def getFrameInfo(frame): """Return (kind,module,locals,globals) for a frame 'kind' is one of "exec", "module", "class", "function call", or "unknown". """ f_locals = frame.f_locals f_globals = frame.f_globals sameNamespace = f_locals is f_globals hasModule = '__module__' in f_locals hasName = '__name__' in f_globals sameName = hasModule and hasName sameName = sameName and f_globals['__name__']==f_locals['__module__'] module = hasName and sys.modules.get(f_globals['__name__']) or None namespaceIsModule = module and module.__dict__ is f_globals frameinfo = inspect.getframeinfo(frame) try: sourceline = frameinfo[3][0].strip() except: # dont understand circumstance here, 3rdparty code without comment sourceline = frameinfo[3] codeinfo = frameinfo[0], frameinfo[1], frameinfo[2], sourceline if not namespaceIsModule: # some kind of funky exec kind = "exec" # don't know how to repeat this scenario elif sameNamespace and not hasModule: kind = "module" elif sameName and not sameNamespace: kind = "class" elif not sameNamespace: kind = "function call" else: # How can you have f_locals is f_globals, and have '__module__' set? # This is probably module-level code, but with a '__module__' variable. kind = "unknown" return kind, module, f_locals, f_globals, codeinfo venusian-1.0a8/PKG-INFO0000664000175000017500000002060012132727372015332 0ustar chrismchrism00000000000000Metadata-Version: 1.1 Name: venusian Version: 1.0a8 Summary: A library for deferring decorator actions Home-page: http://pylonsproject.org Author: Chris McDonough, Agendaless Consulting Author-email: pylons-devel@googlegroups.com License: BSD-derived (http://www.repoze.org/LICENSE.txt) Description: venusian ======== Venusian is a library which allows framework authors to defer decorator actions. Instead of taking actions when a function (or class) decorator is executed at import time, you can defer the action usually taken by the decorator until a separate "scan" phase. See the "docs" directory of the package or the online documentation at http://docs.pylonsproject.org/projects/venusian/en/latest/ 1.0a8 (2013-04-15) ------------------ - Pass ``ignore`` argument along recursively to ``walk_packages`` so custom ignore functions will ignore things recursively. See https://github.com/Pylons/venusian/pull/16 - Don't run tox tests under Python 2.4 anymore (tox no longer supports 2.4). 1.0a7 (2012-08-25) ------------------ - Venusian now works on Python 3.3b2+ (importlib-based). - Use nose-exclude instead of relying on fragile module-scope code to ensure we don't get errors resulting from import of fixture code during "nosetests". - Bug fix: no longer suppress ``ImportError`` while scanning by default. If you want to suppress ``ImportError`` while scanning, you'll now need use an ``onerror`` callback as described in the documentation. 1.0a6 (2012-04-23) ------------------ - Don't ignore decorated objects within their original locations if they happen to be imported into another module (remove ``seen`` set from invoke in venusian scanning). See https://github.com/Pylons/venusian/pull/13 . 1.0a5 (2012-04-21) ------------------ - Slightly less sucky way to ignore objects during scanning that are only imported into a module but not actually defined there. See 1.0a4 change notes for rationale. Now instead of checking whether the module of the *scanned object* matches the module being scanned, we check whether the module of the *Venusian attachment* matches the module being scanned. This allows some genuine uses of imported objects as Venusian scan targets while preventing inappropriate double-scanning of objects that have a venusian attachment which just happen to be imported into other scanned modules. - Add ``dev`` and ``docs`` setup.py commands (ala Pyramid). 1.0a4 (2012-04-16) ------------------ - Attempt to ignore objects during scanning that are only imported into a module but not actually defined there. This is a semantics change, but it's the right thing to do, because I found myself facing a situation like this:: # in a module named "one" from two import anotheradecoratedthing @adecorator def adecoratedthing(): pass # and scanning both modules scan('one') scan('two') In this case you'd wind up with two repeated registrations for "anotherdecoratedthing", which isn't what anyone expects. 1.0a3 (2012-02-08) ------------------ - Add an ``ignore`` argument to the ``scan`` method of a ``Scanner``. This argument allows a user to ignore packages, modules, and global objects by name during a ``scan``. See the "ignore Scan Argument" in the narrative documentation for more details. 1.0a2 (2011-09-02) ------------------ - Close ImpLoader file handle to avoid resource warnings on Python 3. 1.0a1 (2011-08-27) ------------------ - Python 3 compatibility. - Allow an ``onerror`` callback to be passed to ``Scanner.scan()``. 0.9 (2011-06-18) ---------------- - Prevent corner case scan-time exception when trying to introspect insane module-scope objects. See https://github.com/Pylons/venusian/issues/5 . 0.8 (2011-04-30) ---------------- - Normal "setup.py test" can't support running the venusian tests under py 2.4 or 2.5; when it scans the 'classdecorators' fixture, it barfs. To get around this, we used to depend on ``nose`` in ``setup_requires`` and tell "setup.py test" to use nose by setting test_suite to "nose.collector" but we can't anymore because folks use Venusian in systems which install from pip bundles; pip bundles do not support setup_requires. So, sorry, we're painted into a corner; at this point you just have to know to install nose and run "setup.py nosetests" rather than "setup.py test". Or just run "tox" which tests it under all Pythons. 0.7 (2011-03-16) ---------------- - Use Pylons theme in documentation. - Fix orphaned pyc test on pypy. - Fix GitHub Issue #1: subclasses of decorated classes that do not have any decorations should not inherit the decorations of their parent classes. - Fix GitHub Issue #2: scans should only "find" each object once per scan, regardless of how many modules that object is imported into. 0.6 (2011-01-09) ---------------- - Some metaclasses (Elixir's) don't raise an AttributeError when asked for a nonexistent attribute during a scan. We now catch all exceptions when interrogating an object for ``__venusian_callbacks__`` rather than just AttributeError. 0.5 (2010-12-19) ---------------- - Make ``codeinfo`` attribute available as an attribute of the AttachInfo object. It will be a tuple in the form ``(filename, lineno, function, sourceline)`` representing the context of the venusian decorator. Eg. ``('/home/chrism/projects/venusian/tests/test_advice.py', 81, 'testCallInfo', 'add_handler(foo, bar)')`` 0.4 (2010-09-03) ---------------- - Bug fix: when a venusian decorator used as a class decorator was used against both a class *and* a subclass of that class, the superclass and subclass would effectively share the same set of callbacks. This was not the intent: each class declaration should have its own local set of callbacks; callbacks added via decorations should not be inherited, and a superclass should not receive its subclass' decorations. - Arrange test fixtures into a single directory. 0.3 (2010-06-24) ---------------- - Ignore orphaned modules (``.pyc`` or ``.pyo`` files without a corresponding ``.py`` file) during a scan. 0.2 (2010-04-18) ---------------- - Add the concept of scan categories (see the "Scan Categories" section of the documentation) to allow an application to make use of more than one Venusian-using framework simultaneously. 0.1 (2010-02-15) ---------------- - Initial release. Keywords: web wsgi zope Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: Python :: Implementation :: Jython venusian-1.0a8/COPYRIGHT.txt0000644000175000017500000000015511526652542016351 0ustar chrismchrism00000000000000Copyright (c) 2011 Agendaless Consulting and Contributors. (http://www.agendaless.com), All Rights Reserved venusian-1.0a8/docs/0000775000175000017500000000000012132727372015167 5ustar chrismchrism00000000000000venusian-1.0a8/docs/index.rst0000664000175000017500000004740012132726677017044 0ustar chrismchrism00000000000000Venusian ======== Venusian is a library which allows you to defer the action of decorators. Instead of taking actions when a function, method, or class decorator is executed at import time, you can defer the action until a separate "scan" phase. This library is most useful for framework authors. It is compatible with CPython versions 2.4, 2.5, 2.6, 2.7, and 3.2. It also is known to work on PyPy 1.5 and Jython 2.5.2. .. note:: The name "Venusian" is a riff on a library named :term:`Martian` (which had its genesis in the :term:`Grok` web framework), from which the idea for Venusian was stolen. Venusian is similar to Martian, but it offers less functionality, making it slightly simpler to use. Overview -------- Offering a decorator that wraps a function, method, or class can be a convenience to your framework's users. But the very purpose of a decorator makes it likely to impede testability of the function or class it decorates: use of a decorator often prevents the function it decorates from being called with the originally passed arguments, or a decorator may modify the return value of the decorated function. Such modifications to behavior are "hidden" in the decorator code itself. For example, let's suppose your framework defines a decorator function named ``jsonify`` which can wrap a function that returns an arbitrary Python data structure and renders it to a JSON serialization: .. code-block:: python :linenos: import json def jsonify(wrapped): def json_wrapper(request): result = wrapped(request) dumped = json.dumps(result) return dumped return json_wrapper Let's also suppose a user has written an application using your framework, and he has imported your jsonify decorator function, and uses it to decorate an application function: .. code-block:: python :linenos: from theframework import jsonify @jsonify def logged_in(request): return {'result':'Logged in'} As a result of an import of the module containing the ``logged_in`` function, a few things happen: - The user's ``logged_in`` function is replaced by the ``json_wrapper`` function. - The only reference left to the original ``logged_in`` function is inside the frame stack of the call to the ``jsonify`` decorator. This means, from the perspective of the application developer that the original ``logged_in`` function has effectively "disappeared" when it is decorated with your ``jsonify`` decorator. Without bothersome hackery, it can no longer be imported or retrieved by its original author. More importantly, it also means that if the developer wants to unit test the ``logged_in`` function, he'll need to do so only indirectly: he'll need to call the ``json_wrapper`` wrapper decorator function and test that the json returned by the function contains the expected values. This will often imply using the ``json.loads`` function to turn the result of the function *back* into a Python dictionary from the JSON representation serialized by the decorator. If the developer is a stickler for unit testing, however, he'll want to test *only* the function he has actually defined, not the wrapper code implied by the decorator your framework has provided. This is the very definition of unit testing (testing a "unit" without any other integration with other code). In this case, it is also more convenient for him to be able to test the function without the decorator: he won't need to use the ``json.loads`` function to turn the result back into a dictionary to make test assertions against. It's likely such a developer will try to find ways to get at the original function for testing purposes. To do so, he might refactor his code to look like this: .. code-block:: python :linenos: from theframework import jsonify @jsonify def logged_in(request): return _logged_in(request) def _logged_in(request): return {'result':'Logged in'} Then in test code he might import only the ``_logged_in`` function instead of the decorated ``logged_in`` function for purposes of unit testing. In such a scenario, the concentious unit testing app developer has to define two functions for each decorated function. If you're thinking "that looks pretty tedious", you're right. To give the intrepid tester an "out", you might be tempted as a framework author to leave a reference to the original function around somewhere that the unit tester can import and use only for testing purposes. You might modify the ``jsonify`` decorator like so in order to do that: .. code-block:: python :linenos: import json def jsonify(wrapped): def json_wrapper(request): result = wrapped(request) dumped = json.dumps(result) return dumped json_wrapper.original_function = wrapped return json_wrapper The line ``json_wrapper.original_function = wrapped`` is the interesting one above. It means that the application developer has a chance to grab a reference to his original function: .. code-block:: python :linenos: from myapp import logged_in result = logged_in.original_func(None) self.assertEqual(result['result'], 'Logged in') That works. But it's just a little weird. Since the ``jsonify`` decorator function has been imported by the developer from a module in your framework, the developer probably shouldn't really need to know how it works. If he needs to read its code, or understand documentation about how the decorator functions for testing purposes, your framework *might* be less valuable to him on some level. This is arguable, really. If you use some consistent pattern like this for all your decorators, it might be a perfectly reasonable solution. However, what if the decorators offered by your framework were passive until activated explicitly? This is the promise of using Venusian within your decorator implementations. You may use Venusian within your decorators to associate a wrapped function, class, or method with a callback. Then you can return the originally wrapped function. Instead of your decorators being "active", the callback associated with the decorator is passive until a "scan" is initiated. Using Venusian -------------- The most basic use of Venusian within a decorator implementation is demonstrated below. .. code-block:: python :linenos: import venusian def jsonify(wrapped): def callback(scanner, name, ob): print 'jsonified' venusian.attach(wrapped, callback) return wrapped As you can see, this decorator actually calls into venusian, but then simply returns the wrapped object. Effectively this means that this decorator is "passive" when the module is imported. Usage of the decorator: .. code-block:: python :linenos: from theframework import jsonify @jsonify def logged_in(request): return {'result':'Logged in'} Note that when we import and use the function, the fact that it is decorated with the ``jsonify`` decorator is immaterial. Our decorator doesn't actually change its behavior. .. code-block:: python :linenos: >>> from theapp import logged_in >>> logged_in() {'result':'Logged in'} >>> This is the intended result. During unit testing, the original function can be imported and tested despite the fact that it has been wrapped with a decorator. However, we can cause something to happen when we invoke a :term:`scan`. .. code-block:: python :linenos: import venusian import theapp scanner = venusian.Scanner() scanner.scan(theapp) Above we've imported a module named ``theapp``. The ``logged_in`` function which we decorated with our ``jsonify`` decorator lives in this module. We've also imported the :mod:`venusian` module, and we've created an instance of the :class:`venusian.Scanner` class. Once we've created the instance of :class:`venusian.Scanner`, we invoke its :meth:`venusian.Scanner.scan` method, passing the ``theapp`` module as an argument to the method. Here's what happens as a result of invoking the :meth:`venusian.Scanner.scan` method: #. Every object defined at module scope within the ``theapp`` Python module will be inspected to see if it has had a Venusian callback attached to it. #. For every object that *does* have an Venusian callback attached to it, the callback is called. We could have also passed the ``scan`` method a Python *package* instead of a module. This would recursively import each module in the package (as well as any modules in subpackages), looking for callbacks. .. note:: During scan, the only Python files that are processed are Python *source* (``.py``) files. Compiled Python files (``.pyc``, ``.pyo`` files) without a corresponding source file are ignored. In our case, because the callback we defined within the ``jsonify`` decorator function prints ``jsonified`` when it is invoked, which means that the word ``jsonified`` will be printed to the console when we cause :meth:`venusian.Scanner.scan` to be invoked. How is this useful? It's not! At least not yet. Let's create a more realistic example. Let's change our ``jsonify`` decorator to perform a more useful action when a scan is invoked by changing the body of its callback. .. code-block:: python :linenos: import venusian def jsonify(wrapped): def callback(scanner, name, ob): def jsonified(request): result = wrapped(request) return json.dumps(result) scanner.registry.add(name, jsonified) venusian.attach(wrapped, callback) return wrapped Now if we invoke a scan, we'll get an error: .. code-block:: python :linenos: import venusian import theapp scanner = venusian.Scanner() scanner.scan(theapp) AttributeError: Scanner has no attribute 'registry'. The :class:`venusian.Scanner` class constructor accepts any key-value pairs; for each key/value pair passed to the scanner's constructor, an attribute named after the key which points at the value is added to the scanner instance. So when you do: .. code-block:: python :linenos: import venusian scanner = venusian.Scanner(a=1) Thereafter, ``scanner.a`` will equal the integer 1. Any number of key-value pairs can be passed to a scanner. The purpose of being able to pass arbitrary key/value pairs to a scanner is to allow cooperating decorator callbacks to access these values: each callback is passed the ``scanner`` constructed when a scan is invoked. Let's fix our example by creating an object named ``registry`` that we'll pass to our scanner's constructor: .. code-block:: python :linenos: import venusian import theapp class Registry(object): def __init__(self): self.registered = [] def add(self, name, ob): self.registered.append((name, ob)) register = Register() scanner = venusian.Scanner(registry=registry) scanner.scan(theapp) At this point, we have a system which, during a scan, for each object that is wrapped with a Venusian-aware decorator, a tuple will be appended to the ``registered`` attribute of a ``Registry`` object. The first element of the tuple will be the decorated object's name, the second element of the tuple will be a "truly" decorated object. In our case, this will be a jsonify-decorated callable. Our framework can then use the information in the registry to decide which view function to call when a request comes in. Venusian callbacks must accept three arguments: ``scanner`` This will be the instance of the scanner that has had its ``scan`` method invoked. ``name`` This is the module-level name of the object being decorated. ``ob`` This is the object being decorated if it's a function or an instance; if the object being decorated is a *method*, however, this value will be the *class*. If you consider that the decorator and the scanner can cooperate, and can perform arbitrary actions together, you can probably imagine a system where a registry will be populated that informs some higher-level system (such as a web framework) about the available decorated functions. Scan Categories --------------- Because an application may use two separate Venusian-using frameworks, Venusian allows for the concept of "scan categories". The :func:`venusian.attach` function accepts an additional argument named ``category``. For example: .. code-block:: python :linenos: import venusian def jsonify(wrapped): def callback(scanner, name, ob): def jsonified(request): result = wrapped(request) return json.dumps(result) scanner.registry.add(name, jsonified) venusian.attach(wrapped, callback, category='myframework') return wrapped Note the ``category='myframework'`` argument in the call to :func:`venusian.attach`. This tells Venusian to attach the callback to the wrapped object under the specific scan category ``myframework``. The default scan category is ``None``. Later, during :meth:`venusian.Scanner.scan`, a user can choose to activate all the decorators associated only with a particular set of scan categories by passing a ``categories`` argument. For example: .. code-block:: python :linenos: import venusian scanner = venusian.Scanner(a=1) scanner.scan(theapp, categories=('myframework',)) The default ``categories`` argument is ``None``, which means activate all Venusian callbacks during a scan regardless of their category. ``onerror`` Scan Callback ------------------------- .. versionadded:: 1.0 By default, when Venusian scans a package, it will propagate all exceptions raised while attempting to import code. You can use an ``onerror`` callback argument to :meth:`venusian.Scanner.scan` to change this behavior. The ``onerror`` argument should either be ``None`` or a callback function which behaves the same way as the ``onerror`` callback function described in http://docs.python.org/library/pkgutil.html#pkgutil.walk_packages . Here's an example ``onerror`` callback that ignores all :exc:`ImportError` exceptions: .. code-block:: python :linenos: import sys def onerror(name): if not issubclass(sys.exc_info()[0], ImportError): raise # reraise the last exception Here's how we'd use this callback: .. code-block:: python :linenos: import venusian scanner = venusian.Scanner() scanner.scan(theapp, onerror=onerror) The ``onerror`` callback should execute ``raise`` at some point if any exception is to be propagated, otherwise it can simply return. The ``name`` passed to ``onerror`` is the module or package dotted name that could not be imported due to an exception. ``ignore`` Scan Argument ------------------------ .. versionadded:: 1.0a3 The ``ignore`` to ``scan`` allows you to ignore certain modules, packages, or global objects during a scan. It should be a sequence containing strings and/or callables that will be used to match against the full dotted name of each object encountered during the scanning process. If the ignore value you provide matches a package name, global objects contained by that package as well any submodules and subpackages of the package (and any global objects contained by them) will be ignored. If the ignore value you provide matches a module name, any global objects in that module will be ignored. If the ignore value you provide matches a global object that lives in a package or module, only that particular global object will be ignored. The sequence can contain any of these three types of objects: - A string representing a full dotted name. To name an object by dotted name, use a string representing the full dotted name. For example, if you want to ignore the ``my.package`` package and any of its subobjects during the scan, pass ``ignore=['my.package']``. If the string matches a global object (e.g. ``ignore=['my.package.MyClass']``), only that object will be ignored and the rest of the objects in the module or package that contains the object will be processed. - A string representing a relative dotted name. To name an object relative to the ``package`` passed to this method, use a string beginning with a dot. For example, if the ``package`` you've passed is imported as ``my.package``, and you pass ``ignore=['.mymodule']``, the ``my.package.mymodule`` module and any of its subobjects will be omitted during scan processing. If the string matches a global object (e.g. ``ignore=['my.package.MyClass']``), only that object will be ignored and the rest of the objects in the module or package that contains the object will be processed. - A callable that accepts a full dotted name string of an object as its single positional argument and returns ``True`` or ``False``. If the callable returns ``True`` or anything else truthy, the module, package, or global object is ignored, if it returns ``False`` or anything else falsy, it is not ignored. If the callable matches a package name, the package as well as any of that package's submodules and subpackages (recursively) will be ignored. If the callable matches a module name, that module and any of its contained global objects will be ignored. If the callable mactches a global object name, only that object name will be ignored. For example, if you want to skip all packages, modules, and global objects that have a full dotted name that ends with the word "tests", you can use ``ignore=[re.compile('tests$').search]``. Here's an example of how we might use the ``ignore`` argument to ``scan`` to ignore an entire package (and any of its submodules and subpackages) by absolute dotted name: .. code-block:: python :linenos: import venusian scanner = venusian.Scanner() scanner.scan(theapp, ignore=['theapp.package']) Here's an example of how we might use the ``ignore`` argument to ``scan`` to ignore an entire package (and any of its submodules and subpackages) by relative dotted name (``theapp.package``): .. code-block:: python :linenos: import venusian scanner = venusian.Scanner() scanner.scan(theapp, ignore=['.package']) Here's an example of how we might use the ``ignore`` argument to ``scan`` to ignore a particular class object: .. code-block:: python :linenos: import venusian scanner = venusian.Scanner() scanner.scan(theapp, ignore=['theapp.package.MyClass']) Here's an example of how we might use the ``ignore`` argument to ``scan`` to ignore any module, package, or global object that has a name which ends with the string ``tests``: .. code-block:: python :linenos: import re import venusian scanner = venusian.Scanner() scanner.scan(theapp, ignore=[re.compile('tests$').search]) You can mix and match the three types in the list. For example, ``scanner.scan(my, ignore=['my.package', '.someothermodule', re.compile('tests$').search])`` would cause ``my.package`` (and all its submodules and subobjects) to be ignored, ``my.someothermodule`` to be ignored, and any modules, packages, or global objects found during the scan that have a full dotted path that ends with the word ``tests`` to be ignored beneath the ``my`` package. Packages and modules matched by any ignore in the list will not be imported, and their top-level code will not be run as a result. Limitations and Audience ------------------------ Venusian is not really a tool that is maximally useful to an application developer. It would be a little silly to use it every time you needed a decorator. Instead, it's most useful for framework authors, in order to be able to say to their users "the frobozz decorator doesn't change the output of your function at all" in documentation. This is a lot easier than telling them how to test methods/functions/classes decorated by each individual decorator offered by your frameworks. API Documentation / Glossary ---------------------------- .. toctree:: :maxdepth: 2 api.rst glossary.rst Indices and tables ------------------ * :ref:`glossary` * :ref:`modindex` * :ref:`search` venusian-1.0a8/docs/glossary.rst0000644000175000017500000000052711526650777017600 0ustar chrismchrism00000000000000.. _glossary: Glossary ======== .. glossary:: :sorted: scan Walk a module or package executing callbacks defined by venusian-aware decorators along the way. Martian The package venusian was inspired by, part of the :term:`Grok` project. Grok A Zope-based `web framework `. venusian-1.0a8/docs/.static/0000775000175000017500000000000012132727372016534 5ustar chrismchrism00000000000000venusian-1.0a8/docs/.static/logo_hi.gif0000644000175000017500000000752411526650777020663 0ustar chrismchrism00000000000000GIF89a3BBBًzι<<>>ԾԾտѻԿLKIԶľBBA@@?|||??>AAAȼEEEHGEڷcccյųMMM˽͸̾vur@??YYYxCCCþihc:::ӽ!,3 H*\ȰÇ#JHŋ3jȱdžIrCȲ˅.ȜI^F͟?ѬIhE*XMJu! j%D. MjJ?_Ŗ@D|"݅t['Y EHG8._7CW"7)Tv'r֦+RcN+'n c ZMd#g׎9f _HrA\FSAUR`rMq`E"Wҥ&7ĄB5BU t fM?~O?5WGՂ|,ȢC4D54Xc8+pA , 9$ C- Cy  & E(q0 t$ ܴf(D.@>yXM4+ 7H‚JhTBE BZ0'@YN16tQbP X*:ab lTåuQf jJ dBDNC tP<P _PPRlpʴ ;D2$$aQCk72/Dx0(g& J#̐A:r AD!8(a 8žT4΃»@ 3(h.@B氽T@L;|B١sC@khQ:QęCt 0`=GJX-W|A !,@3zrasA rnqRh2[K\ Djئ<9E*Z8p@!p.n B x| ՐtS%=a,$`h %@A'6xB †) 6x;50bLPxF+90bA00)4l#QC-D&lp}PGvG<ڠ'cC!u!,._h ZN_a $ @Ҏ+P2R(ՇMosP/`qg.b Ed_ D 2,%vvm!^+P/yz"<S?yH€WPR C4;@`b9-@_0jj`!`\8lyxw WH0 `cPaQ 0$ W}GJ l   aCЍpqQ@PA dvyGP pYP E" (P{ 0@@ 1 F@d s @ 900) ٟ ;venusian-1.0a8/docs/.static/repoze.css0000644000175000017500000000077511526650777020573 0ustar chrismchrism00000000000000@import url('default.css'); body { background-color: #006339; } div.document { background-color: #dad3bd; } div.sphinxsidebar h3, h4, h5, a { color: #127c56 !important; } div.related { color: #dad3bd !important; background-color: #00744a; } div.related a { color: #dad3bd !important; } /* override the justify text align of the default */ div.body p { text-align: left !important; } /* fix google chrome
 tag renderings */

pre {
   line-height: normal !important;
}
venusian-1.0a8/docs/conf.py0000664000175000017500000001541512132727163016472 0ustar  chrismchrism00000000000000# -*- coding: utf-8 -*-
#
# venusian documentation build configuration file
#
# This file is execfile()d with the current directory set to its containing
# dir.
#
# The contents of this file are pickled, so don't put values in the
# namespace that aren't pickleable (module imports are okay, they're
# removed automatically).
#
# All configuration values have a default value; values that are commented
# out serve to show the default value.

import sys
import os
import datetime

# Add and use Pylons theme
if 'sphinx-build' in ' '.join(sys.argv): # protect against dumb importers
    from subprocess import call, Popen, PIPE

    p = Popen('which git', shell=True, stdout=PIPE)
    git = p.stdout.read().strip()
    cwd = os.getcwd()
    _themes = os.path.join(cwd, '_themes')

    if not os.path.isdir(_themes):
        call([git, 'clone', 'git://github.com/Pylons/pylons_sphinx_theme.git',
                '_themes'])
    else:
        os.chdir(_themes)
        call([git, 'checkout', 'master'])
        call([git, 'pull'])
        os.chdir(cwd)

    sys.path.append(os.path.abspath('_themes'))

    parent = os.path.dirname(os.path.dirname(__file__))
    sys.path.append(os.path.abspath(parent))
    wd = os.getcwd()
    os.chdir(parent)
    sys.path.append(parent)

# Options for HTML output
# -----------------------

sys.path.append(os.path.abspath('_themes'))
html_theme_path = ['_themes']
html_theme = 'pylons'
html_theme_options = dict(github_url='https://github.com/Pylons/venusian')

# If your extensions are in another directory, add it here. If the
# directory is relative to the documentation root, use os.path.abspath to
# make it absolute, like shown here.
#sys.path.append(os.path.abspath('some/directory'))

# General configuration
# ---------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc']

# Add any paths that contain templates here, relative to this directory.
templates_path = ['.templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General substitutions.
project = 'venusian'
year = datetime.datetime.now().year
copyright = '2012-%s Pylons Project ' % year

# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
#
# The short X.Y version.
version = '1.0a8'
# The full version, including alpha/beta/rc tags.
release = version

# There are two options for replacing |today|: either, you set today to
# some non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
today_fmt = '%B %d, %Y'

# List of documents that shouldn't be included in the build.
#unused_docs = []

# List of directories, relative to source directories, that shouldn't be
# searched for source files.
#exclude_dirs = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

exclude_patterns = ['_themes/README.rst',]

# Options for HTML output
# -----------------------

html_theme_options = {
    'github_url': 'https://github.com/Pylons/venusian'
}

# The style sheet to use for HTML and HTML Help pages. A file of that name
# must exist either in Sphinx' static/ path, or in one of the custom paths
# given in html_static_path.
#html_style = 'pyramid.css'

# The name for this set of Sphinx documents.  If None, it defaults to
# " v documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as
# html_title.
#html_short_title = None

# The name of an image file (within the static path) to place at the top of
# the sidebar.
#html_logo = '.static/logo_hi.gif'

# The name of an image file (within the static path) to use as favicon of
# the docs.  This file should be a Windows icon file (.ico) being 16x16 or
# 32x32 pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets)
# here, relative to this directory. They are copied after the builtin
# static files, so a file named "default.css" will overwrite the builtin
# "default.css".
#html_static_path = ['.static']

# If not '', a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_use_modindex = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, the reST sources are included in the HTML build as
# _sources/.
#html_copy_source = True

# If true, an OpenSearch description file will be output, and all pages
# will contain a  tag referring to it.  The value of this option must
# be the base URL from which the finished HTML is served.
#html_use_opensearch = ''

# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''

# Output file base name for HTML help builder.
htmlhelp_basename = 'atemplatedoc'


# Options for LaTeX output
# ------------------------

# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'

# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, document class [howto/manual]).
latex_documents = [
  ('index', 'atemplate.tex', 'venusian Documentation',
   'Pylons Project', 'manual'),
]

# The name of an image file (relative to this directory) to place at the
# top of the title page.
latex_logo = '.static/logo_hi.gif'

# For "manual" documents, if this is true, then toplevel headings are
# parts, not chapters.
#latex_use_parts = False

# Additional stuff for the LaTeX preamble.
#latex_preamble = ''

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_use_modindex = True
venusian-1.0a8/docs/api.rst0000644000175000017500000000035111526650777016501 0ustar  chrismchrism00000000000000API Documentation for Venusian
==============================

.. automodule:: venusian

  .. autoclass:: Scanner

     .. automethod:: scan

  .. autoclass:: AttachInfo

  .. autofunction:: attach(wrapped, callback, category=None)

venusian-1.0a8/docs/Makefile0000644000175000017500000000521511714637073016633 0ustar  chrismchrism00000000000000# Makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    = -W
SPHINXBUILD   = sphinx-build
PAPER         =

# Internal variables.
PAPEROPT_a4     = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS   = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

.PHONY: help clean html web pickle htmlhelp latex changes linkcheck

help:
	@echo "Please use \`make ' where  is one of"
	@echo "  html      to make standalone HTML files"
	@echo "  pickle    to make pickle files (usable by e.g. sphinx-web)"
	@echo "  htmlhelp  to make HTML files and a HTML help project"
	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
	@echo "  changes   to make an overview over all changed/added/deprecated items"
	@echo "  linkcheck to check all external links for integrity"

clean:
	-rm -rf _build/*

html:
	mkdir -p _build/html _build/doctrees
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
	@echo
	@echo "Build finished. The HTML pages are in _build/html."

text:
	mkdir -p _build/text _build/doctrees
	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) _build/text
	@echo
	@echo "Build finished. The HTML pages are in _build/text."

pickle:
	mkdir -p _build/pickle _build/doctrees
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
	@echo
	@echo "Build finished; now you can process the pickle files or run"
	@echo "  sphinx-web _build/pickle"
	@echo "to start the sphinx-web server."

web: pickle

htmlhelp:
	mkdir -p _build/htmlhelp _build/doctrees
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
	@echo
	@echo "Build finished; now you can run HTML Help Workshop with the" \
	      ".hhp project file in _build/htmlhelp."

latex:
	mkdir -p _build/latex _build/doctrees
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
	cp _static/*.png _build/latex
	./convert_images.sh
	cp _static/latex-warning.png _build/latex
	cp _static/latex-note.png _build/latex
	@echo
	@echo "Build finished; the LaTeX files are in _build/latex."
	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
	      "run these through (pdf)latex."

changes:
	mkdir -p _build/changes _build/doctrees
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes
	@echo
	@echo "The overview file is in _build/changes."

linkcheck:
	mkdir -p _build/linkcheck _build/doctrees
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck
	@echo
	@echo "Link check complete; look for any errors in the above output " \
	      "or in _build/linkcheck/output.txt."

epub:
	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) _build/epub
	@echo
	@echo "Build finished. The epub file is in _build/epub."

venusian-1.0a8/setup.cfg0000664000175000017500000000050612132727372016061 0ustar  chrismchrism00000000000000[easy_install]
zip_ok = false

[nosetests]
match = ^test
where = venusian
nocapture = 1
cover-package = venusian
cover-erase = 1
exclude-dir = venusian/tests/fixtures

[aliases]
dev = develop easy_install venusian[testing]
docs = develop easy_install venusian[docs]

[egg_info]
tag_build = 
tag_date = 0
tag_svn_revision = 0

venusian-1.0a8/CONTRIBUTORS.txt0000644000175000017500000001107611622562555016743 0ustar  chrismchrism00000000000000Pylons Project Contributor Agreement
====================================

The submitter agrees by adding his or her name within the section below named
"Contributors" and submitting the resulting modified document to the
canonical shared repository location for this software project (whether
directly, as a user with "direct commit access", or via a "pull request"), he
or she is signing a contract electronically.  The submitter becomes a
Contributor after a) he or she signs this document by adding their name
beneath the "Contributors" section below, and b) the resulting document is
accepted into the canonical version control repository.

Treatment of Account
---------------------

Contributor will not allow anyone other than the Contributor to use his or
her username or source repository login to submit code to a Pylons Project
source repository. Should Contributor become aware of any such use,
Contributor will immediately by notifying Agendaless Consulting.
Notification must be performed by sending an email to
webmaster@agendaless.com.  Until such notice is received, Contributor will be
presumed to have taken all actions made through Contributor's account. If the
Contributor has direct commit access, Agendaless Consulting will have
complete control and discretion over capabilities assigned to Contributor's
account, and may disable Contributor's account for any reason at any time.

Legal Effect of Contribution
----------------------------

Upon submitting a change or new work to a Pylons Project source Repository (a
"Contribution"), you agree to assign, and hereby do assign, a one-half
interest of all right, title and interest in and to copyright and other
intellectual property rights with respect to your new and original portions
of the Contribution to Agendaless Consulting. You and Agendaless Consulting
each agree that the other shall be free to exercise any and all exclusive
rights in and to the Contribution, without accounting to one another,
including without limitation, the right to license the Contribution to others
under the Repoze Public License. This agreement shall run with title to the
Contribution. Agendaless Consulting does not convey to you any right, title
or interest in or to the Program or such portions of the Contribution that
were taken from the Program. Your transmission of a submission to the Pylons
Project source Repository and marks of identification concerning the
Contribution itself constitute your intent to contribute and your assignment
of the work in accordance with the provisions of this Agreement.

License Terms
-------------

Code committed to the Pylons Project source repository (Committed Code) must
be governed by the Repoze Public License (http://repoze.org/LICENSE.txt, aka
"the RPL") or another license acceptable to Agendaless Consulting.  Until
Agendaless Consulting declares in writing an acceptable license other than
the RPL, only the RPL shall be used.  A list of exceptions is detailed within
the "Licensing Exceptions" section of this document, if one exists.

Representations, Warranty, and Indemnification
----------------------------------------------

Contributor represents and warrants that the Committed Code does not violate
the rights of any person or entity, and that the Contributor has legal
authority to enter into this Agreement and legal authority over Contributed
Code. Further, Contributor indemnifies Agendaless Consulting against
violations.

Cryptography
------------

Contributor understands that cryptographic code may be subject to government
regulations with which Agendaless Consulting and/or entities using Committed
Code must comply. Any code which contains any of the items listed below must
not be checked-in until Agendaless Consulting staff has been notified and has
approved such contribution in writing.

- Cryptographic capabilities or features

- Calls to cryptographic features

- User interface elements which provide context relating to cryptography

- Code which may, under casual inspection, appear to be cryptographic.

Notices
-------

Contributor confirms that any notices required will be included in any
Committed Code.

Licensing Exceptions
====================

None.

List of Contributors
====================

The below-signed are contributors to a code repository that is part of the
project named "Venusian".  Each below-signed contributor has read, understand
and agrees to the terms above in the section within this document entitled
"Pylons Project Contributor Agreement" as of the date beside his or her name.

Contributors
------------

- Chris McDonough, 2011/02/16
- Chris Withers, 2011/03/14
- Joel Bohman, 2011/07/28
venusian-1.0a8/setup.py0000664000175000017500000000554012132727006015747 0ustar  chrismchrism00000000000000##############################################################################
#
# Copyright (c) 2010 Agendaless Consulting and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the BSD-like license at
# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany
# this distribution.  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL
# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND
# FITNESS FOR A PARTICULAR PURPOSE
#
##############################################################################

import os

from setuptools import setup
from setuptools import find_packages

here = os.path.abspath(os.path.dirname(__file__))

try:
    README = open(os.path.join(here, 'README.txt')).read()
    CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
except:
    README = ''
    CHANGES = ''

tests_extras = ['nose', 'nose-exclude', 'coverage']
docs_extras = ['Sphinx', 'repoze.sphinx.autointerface']

setup(name='venusian',
      version='1.0a8',
      description='A library for deferring decorator actions',
      long_description=README + '\n\n' +  CHANGES,
      classifiers=[
        "Development Status :: 3 - Alpha",
        "Intended Audience :: Developers",
        "Programming Language :: Python",
        "Programming Language :: Python :: 2.4",
        "Programming Language :: Python :: 2.5",
        "Programming Language :: Python :: 2.6",
        "Programming Language :: Python :: 2.7",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.2",
        "Programming Language :: Python :: 3.3",
        "Programming Language :: Python :: Implementation :: CPython",
        "Programming Language :: Python :: Implementation :: PyPy",
        "Programming Language :: Python :: Implementation :: Jython",
          ],
      keywords='web wsgi zope',
      author="Chris McDonough, Agendaless Consulting",
      author_email="pylons-devel@googlegroups.com",
      url="http://pylonsproject.org",
      license="BSD-derived (http://www.repoze.org/LICENSE.txt)",
      packages=find_packages(),
      include_package_data=True,
      zip_safe=False,
      extras_require = {
          'testing':tests_extras,
          'docs':docs_extras,
          },
      tests_require = [],
      install_requires = [],
      # Normal "setup.py test" can't support running the venusian tests under
      # py 2.4 or 2.5; when it scans the 'classdecorators' fixture, it
      # barfs.  We can't depend on nose in setup_requires here because folks use
      # this under "pip bundle" which does not support setup_requires.
      # So you just have to know to install nose and run "setup.py nosetests"
      # rather than setup.py test.
      test_suite='venusian',
      entry_points = """\
      """
      )

venusian-1.0a8/tox.ini0000664000175000017500000000121612040053635015542 0ustar  chrismchrism00000000000000[tox]
envlist = 
    py25,py26,py27,py32,jython,pypy,cover

[testenv]
commands = 
    python setup.py nosetests
deps = 
    nose
    nose-exclude

[testenv:jython]
commands = 
   jython setup.py nosetests

[testenv:cover]
basepython =
    python2.6
commands = 
    python setup.py nosetests --with-xunit --with-xcoverage
deps = 
    nose
    coverage==3.4
    nosexcover
    nose-exclude

# we separate coverage into its own testenv because a) "last run wins" wrt
# cobertura jenkins reporting and b) pypy and jython can't handle any
# combination of versions of coverage and nosexcover that i can find.
# coverage <3.4 is required by nosexcover 1.0.4.

venusian-1.0a8/venusian.egg-info/0000775000175000017500000000000012132727372017561 5ustar  chrismchrism00000000000000venusian-1.0a8/venusian.egg-info/entry_points.txt0000644000175000017500000000000612132727371023050 0ustar  chrismchrism00000000000000      venusian-1.0a8/venusian.egg-info/PKG-INFO0000644000175000017500000002060012132727371020651 0ustar  chrismchrism00000000000000Metadata-Version: 1.1
Name: venusian
Version: 1.0a8
Summary: A library for deferring decorator actions
Home-page: http://pylonsproject.org
Author: Chris McDonough, Agendaless Consulting
Author-email: pylons-devel@googlegroups.com
License: BSD-derived (http://www.repoze.org/LICENSE.txt)
Description: venusian
        ========
        
        Venusian is a library which allows framework authors to defer
        decorator actions.  Instead of taking actions when a function (or
        class) decorator is executed at import time, you can defer the action
        usually taken by the decorator until a separate "scan" phase.
        
        See the "docs" directory of the package or the online documentation at
        http://docs.pylonsproject.org/projects/venusian/en/latest/
        
        
        1.0a8 (2013-04-15)
        ------------------
        
        - Pass ``ignore`` argument along recursively to ``walk_packages`` so custom
          ignore functions will ignore things recursively.  See
          https://github.com/Pylons/venusian/pull/16
        
        - Don't run tox tests under Python 2.4 anymore (tox no longer supports 2.4).
        
        1.0a7 (2012-08-25)
        ------------------
        
        - Venusian now works on Python 3.3b2+ (importlib-based).
        
        - Use nose-exclude instead of relying on fragile module-scope code to ensure
          we don't get errors resulting from import of fixture code during
          "nosetests".
        
        - Bug fix: no longer suppress ``ImportError`` while scanning by default.  If
          you want to suppress ``ImportError`` while scanning, you'll now need use an
          ``onerror`` callback as described in the documentation.
        
        1.0a6 (2012-04-23)
        ------------------
        
        - Don't ignore decorated objects within their original locations if they
          happen to be imported into another module (remove ``seen`` set from invoke
          in venusian scanning).  See https://github.com/Pylons/venusian/pull/13 .
        
        1.0a5 (2012-04-21)
        ------------------
        
        - Slightly less sucky way to ignore objects during scanning that are only
          imported into a module but not actually defined there.  See 1.0a4 change
          notes for rationale.  Now instead of checking whether the module of the
          *scanned object* matches the module being scanned, we check whether the
          module of the *Venusian attachment* matches the module being scanned.  This
          allows some genuine uses of imported objects as Venusian scan targets while
          preventing inappropriate double-scanning of objects that have a venusian
          attachment which just happen to be imported into other scanned modules.
        
        - Add ``dev`` and ``docs`` setup.py commands (ala Pyramid).
        
        1.0a4 (2012-04-16)
        ------------------
        
        - Attempt to ignore objects during scanning that are only imported into a
          module but not actually defined there.  This is a semantics change, but
          it's the right thing to do, because I found myself facing a situation like
          this::
        
            # in a module named "one"
        
            from two import anotheradecoratedthing
            @adecorator
            def adecoratedthing(): pass
        
            # and scanning both modules
            scan('one')
            scan('two')
        
          In this case you'd wind up with two repeated registrations for
          "anotherdecoratedthing", which isn't what anyone expects.
        
        1.0a3 (2012-02-08)
        ------------------
        
        - Add an ``ignore`` argument to the ``scan`` method of a ``Scanner``.  This
          argument allows a user to ignore packages, modules, and global objects by
          name during a ``scan``.  See the "ignore Scan Argument" in the narrative
          documentation for more details.
        
        1.0a2 (2011-09-02)
        ------------------
        
        - Close ImpLoader file handle to avoid resource warnings on Python 3.
        
        1.0a1 (2011-08-27)
        ------------------
        
        - Python 3 compatibility.
        
        - Allow an ``onerror`` callback to be passed to ``Scanner.scan()``.
        
        0.9 (2011-06-18)
        ----------------
        
        - Prevent corner case scan-time exception when trying to introspect insane
          module-scope objects.  See https://github.com/Pylons/venusian/issues/5 .
        
        0.8 (2011-04-30)
        ----------------
        
        - Normal "setup.py test" can't support running the venusian tests under py
          2.4 or 2.5; when it scans the 'classdecorators' fixture, it barfs.  To get
          around this, we used to depend on ``nose`` in ``setup_requires`` and tell
          "setup.py test" to use nose by setting test_suite to "nose.collector" but
          we can't anymore because folks use Venusian in systems which install from
          pip bundles; pip bundles do not support setup_requires.  So, sorry, we're
          painted into a corner; at this point you just have to know to install nose
          and run "setup.py nosetests" rather than "setup.py test".  Or just run
          "tox" which tests it under all Pythons.
        
        0.7 (2011-03-16)
        ----------------
        
        - Use Pylons theme in documentation.
        
        - Fix orphaned pyc test on pypy.
        
        - Fix GitHub Issue #1: subclasses of decorated classes that do not
          have any decorations should not inherit the decorations of their
          parent classes. 
        
        - Fix GitHub Issue #2: scans should only "find" each object once per
          scan, regardless of how many modules that object is imported into.
        
        0.6 (2011-01-09)
        ----------------
        
        - Some metaclasses (Elixir's) don't raise an AttributeError when asked for a
          nonexistent attribute during a scan.  We now catch all exceptions when
          interrogating an object for ``__venusian_callbacks__`` rather than just
          AttributeError.
        
        0.5 (2010-12-19)
        ----------------
        
        - Make ``codeinfo`` attribute available as an attribute of the AttachInfo
          object. It will be a tuple in the form ``(filename, lineno, function,
          sourceline)`` representing the context of the venusian decorator.  Eg.
          ``('/home/chrism/projects/venusian/tests/test_advice.py', 81,
          'testCallInfo', 'add_handler(foo, bar)')``
        
        0.4 (2010-09-03)
        ----------------
        
        - Bug fix: when a venusian decorator used as a class decorator was
          used against both a class *and* a subclass of that class, the
          superclass and subclass would effectively share the same set of
          callbacks.  This was not the intent: each class declaration should
          have its own local set of callbacks; callbacks added via decorations
          should not be inherited, and a superclass should not receive its
          subclass' decorations.
        
        - Arrange test fixtures into a single directory.
        
        0.3 (2010-06-24)
        ----------------
        
        - Ignore orphaned modules (``.pyc`` or ``.pyo`` files without a
          corresponding ``.py`` file) during a scan.
        
        0.2 (2010-04-18)
        ----------------
        
        - Add the concept of scan categories (see the "Scan Categories"
          section of the documentation) to allow an application to make use of
          more than one Venusian-using framework simultaneously.
        
        0.1 (2010-02-15)
        ----------------
        
        - Initial release.
        
Keywords: web wsgi zope
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.4
Classifier: Programming Language :: Python :: 2.5
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.2
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Programming Language :: Python :: Implementation :: Jython
venusian-1.0a8/venusian.egg-info/not-zip-safe0000644000175000017500000000000111557126223022003 0ustar  chrismchrism00000000000000
venusian-1.0a8/venusian.egg-info/dependency_links.txt0000644000175000017500000000000112132727371023624 0ustar  chrismchrism00000000000000
venusian-1.0a8/venusian.egg-info/top_level.txt0000644000175000017500000000001112132727371022300 0ustar  chrismchrism00000000000000venusian
venusian-1.0a8/venusian.egg-info/SOURCES.txt0000644000175000017500000000453712132727372021454 0ustar  chrismchrism00000000000000.gitignore
CHANGES.txt
CONTRIBUTORS.txt
COPYRIGHT.txt
LICENSE.txt
README.txt
rtd.txt
setup.cfg
setup.py
tox.ini
docs/Makefile
docs/api.rst
docs/conf.py
docs/glossary.rst
docs/index.rst
docs/.static/logo_hi.gif
docs/.static/repoze.css
venusian/__init__.py
venusian/advice.py
venusian.egg-info/PKG-INFO
venusian.egg-info/SOURCES.txt
venusian.egg-info/dependency_links.txt
venusian.egg-info/entry_points.txt
venusian.egg-info/not-zip-safe
venusian.egg-info/requires.txt
venusian.egg-info/top_level.txt
venusian/compat/__init__.py
venusian/compat/pkgutil_26.py
venusian/tests/__init__.py
venusian/tests/test_advice.py
venusian/tests/test_venusian.py
venusian/tests/fixtures/__init__.py
venusian/tests/fixtures/category.py
venusian/tests/fixtures/classdecorator.py
venusian/tests/fixtures/inheritance.py
venusian/tests/fixtures/attrerror/__init__.py
venusian/tests/fixtures/attrerror/will_cause_import_error.py
venusian/tests/fixtures/attrerror_package/__init__.py
venusian/tests/fixtures/attrerror_package/will_cause_import_error/__init__.py
venusian/tests/fixtures/import_and_scan/__init__.py
venusian/tests/fixtures/import_and_scan/one.py
venusian/tests/fixtures/import_and_scan/two.py
venusian/tests/fixtures/importerror/__init__.py
venusian/tests/fixtures/importerror/will_cause_import_error.py
venusian/tests/fixtures/importerror_package/__init__.py
venusian/tests/fixtures/importerror_package/will_cause_import_error/__init__.py
venusian/tests/fixtures/importonly/__init__.py
venusian/tests/fixtures/importonly/one.py
venusian/tests/fixtures/importonly/two.py
venusian/tests/fixtures/nested/__init__.py
venusian/tests/fixtures/nested/sub1/__init__.py
venusian/tests/fixtures/nested/sub1/subsub1/__init__.py
venusian/tests/fixtures/nested/sub2/__init__.py
venusian/tests/fixtures/nested/sub2/subsub2/__init__.py
venusian/tests/fixtures/one/__init__.py
venusian/tests/fixtures/one/module.py
venusian/tests/fixtures/one/module2.py
venusian/tests/fixtures/pyc/__init__.py
venusian/tests/fixtures/pyc/module.py
venusian/tests/fixtures/pyc/subpackage/__init__.py
venusian/tests/fixtures/subpackages/__init__.py
venusian/tests/fixtures/subpackages/mod2.py
venusian/tests/fixtures/subpackages/childpackage/__init__.py
venusian/tests/fixtures/subpackages/childpackage/will_cause_import_error.py
venusian/tests/fixtures/two/__init__.py
venusian/tests/fixtures/two/mod1.py
venusian/tests/fixtures/two/mod2.pyvenusian-1.0a8/venusian.egg-info/requires.txt0000664000175000017500000000012112132727371022152 0ustar  chrismchrism00000000000000

[docs]
Sphinx
repoze.sphinx.autointerface

[testing]
nose
nose-exclude
coveragevenusian-1.0a8/CHANGES.txt0000664000175000017500000001313212132726761016051 0ustar  chrismchrism000000000000001.0a8 (2013-04-15)
------------------

- Pass ``ignore`` argument along recursively to ``walk_packages`` so custom
  ignore functions will ignore things recursively.  See
  https://github.com/Pylons/venusian/pull/16

- Don't run tox tests under Python 2.4 anymore (tox no longer supports 2.4).

1.0a7 (2012-08-25)
------------------

- Venusian now works on Python 3.3b2+ (importlib-based).

- Use nose-exclude instead of relying on fragile module-scope code to ensure
  we don't get errors resulting from import of fixture code during
  "nosetests".

- Bug fix: no longer suppress ``ImportError`` while scanning by default.  If
  you want to suppress ``ImportError`` while scanning, you'll now need use an
  ``onerror`` callback as described in the documentation.

1.0a6 (2012-04-23)
------------------

- Don't ignore decorated objects within their original locations if they
  happen to be imported into another module (remove ``seen`` set from invoke
  in venusian scanning).  See https://github.com/Pylons/venusian/pull/13 .

1.0a5 (2012-04-21)
------------------

- Slightly less sucky way to ignore objects during scanning that are only
  imported into a module but not actually defined there.  See 1.0a4 change
  notes for rationale.  Now instead of checking whether the module of the
  *scanned object* matches the module being scanned, we check whether the
  module of the *Venusian attachment* matches the module being scanned.  This
  allows some genuine uses of imported objects as Venusian scan targets while
  preventing inappropriate double-scanning of objects that have a venusian
  attachment which just happen to be imported into other scanned modules.

- Add ``dev`` and ``docs`` setup.py commands (ala Pyramid).

1.0a4 (2012-04-16)
------------------

- Attempt to ignore objects during scanning that are only imported into a
  module but not actually defined there.  This is a semantics change, but
  it's the right thing to do, because I found myself facing a situation like
  this::

    # in a module named "one"

    from two import anotheradecoratedthing
    @adecorator
    def adecoratedthing(): pass

    # and scanning both modules
    scan('one')
    scan('two')

  In this case you'd wind up with two repeated registrations for
  "anotherdecoratedthing", which isn't what anyone expects.

1.0a3 (2012-02-08)
------------------

- Add an ``ignore`` argument to the ``scan`` method of a ``Scanner``.  This
  argument allows a user to ignore packages, modules, and global objects by
  name during a ``scan``.  See the "ignore Scan Argument" in the narrative
  documentation for more details.

1.0a2 (2011-09-02)
------------------

- Close ImpLoader file handle to avoid resource warnings on Python 3.

1.0a1 (2011-08-27)
------------------

- Python 3 compatibility.

- Allow an ``onerror`` callback to be passed to ``Scanner.scan()``.

0.9 (2011-06-18)
----------------

- Prevent corner case scan-time exception when trying to introspect insane
  module-scope objects.  See https://github.com/Pylons/venusian/issues/5 .

0.8 (2011-04-30)
----------------

- Normal "setup.py test" can't support running the venusian tests under py
  2.4 or 2.5; when it scans the 'classdecorators' fixture, it barfs.  To get
  around this, we used to depend on ``nose`` in ``setup_requires`` and tell
  "setup.py test" to use nose by setting test_suite to "nose.collector" but
  we can't anymore because folks use Venusian in systems which install from
  pip bundles; pip bundles do not support setup_requires.  So, sorry, we're
  painted into a corner; at this point you just have to know to install nose
  and run "setup.py nosetests" rather than "setup.py test".  Or just run
  "tox" which tests it under all Pythons.

0.7 (2011-03-16)
----------------

- Use Pylons theme in documentation.

- Fix orphaned pyc test on pypy.

- Fix GitHub Issue #1: subclasses of decorated classes that do not
  have any decorations should not inherit the decorations of their
  parent classes. 

- Fix GitHub Issue #2: scans should only "find" each object once per
  scan, regardless of how many modules that object is imported into.

0.6 (2011-01-09)
----------------

- Some metaclasses (Elixir's) don't raise an AttributeError when asked for a
  nonexistent attribute during a scan.  We now catch all exceptions when
  interrogating an object for ``__venusian_callbacks__`` rather than just
  AttributeError.

0.5 (2010-12-19)
----------------

- Make ``codeinfo`` attribute available as an attribute of the AttachInfo
  object. It will be a tuple in the form ``(filename, lineno, function,
  sourceline)`` representing the context of the venusian decorator.  Eg.
  ``('/home/chrism/projects/venusian/tests/test_advice.py', 81,
  'testCallInfo', 'add_handler(foo, bar)')``

0.4 (2010-09-03)
----------------

- Bug fix: when a venusian decorator used as a class decorator was
  used against both a class *and* a subclass of that class, the
  superclass and subclass would effectively share the same set of
  callbacks.  This was not the intent: each class declaration should
  have its own local set of callbacks; callbacks added via decorations
  should not be inherited, and a superclass should not receive its
  subclass' decorations.

- Arrange test fixtures into a single directory.

0.3 (2010-06-24)
----------------

- Ignore orphaned modules (``.pyc`` or ``.pyo`` files without a
  corresponding ``.py`` file) during a scan.

0.2 (2010-04-18)
----------------

- Add the concept of scan categories (see the "Scan Categories"
  section of the documentation) to allow an application to make use of
  more than one Venusian-using framework simultaneously.

0.1 (2010-02-15)
----------------

- Initial release.
venusian-1.0a8/README.txt0000644000175000017500000000063211743006574015735 0ustar  chrismchrism00000000000000venusian
========

Venusian is a library which allows framework authors to defer
decorator actions.  Instead of taking actions when a function (or
class) decorator is executed at import time, you can defer the action
usually taken by the decorator until a separate "scan" phase.

See the "docs" directory of the package or the online documentation at
http://docs.pylonsproject.org/projects/venusian/en/latest/
venusian-1.0a8/.gitignore0000644000175000017500000000031112015214224016203 0ustar  chrismchrism00000000000000*.egg
*.egg-info
*.pyc
*$py.class
*.pt.py
*.txt.py
*~
.coverage
.tox/
nosetests.xml
pyramid/coverage.xml
tutorial.db
env*/
jyenv/
pypyenv/
build/
dist/
venusian/coverage.xml
docs/_build/
docs/_themes/
venusian-1.0a8/rtd.txt0000644000175000017500000000003411714637055015567 0ustar  chrismchrism00000000000000repoze.sphinx.autointerface
venusian-1.0a8/LICENSE.txt0000644000175000017500000000337711526650777016104 0ustar  chrismchrism00000000000000License

  A copyright notice accompanies this license document that identifies
  the copyright holders.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

  1.  Redistributions in source code must retain the accompanying
      copyright notice, this list of conditions, and the following
      disclaimer.

  2.  Redistributions in binary form must reproduce the accompanying
      copyright notice, this list of conditions, and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.

  3.  Names of the copyright holders must not be used to endorse or
      promote products derived from this software without prior
      written permission from the copyright holders.

  4.  If any files are modified, you must cause the modified files to
      carry prominent notices stating that you changed the files and
      the date of any change.

  Disclaimer

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND
    ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
    TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    SUCH DAMAGE.