martian-0.14/0000755000175000017500000000000011464246723010724 5ustar jwjwmartian-0.14/CHANGES.txt0000644000175000017500000002414411464246706012543 0ustar jwjwCHANGES ******* 0.14 (2010-11-03) ================= Feature changes --------------- * The computation of the default value for a directive can now be defined inside the directive class definition. Whenever there is a ``get_default`` classmethod, it is used for computing the default:: class name(Directive): scope = CLASS store = ONCE @classmethod def get_default(cls, component, module=None, **data): return component.__name__.lower() When binding the directive, the default-default behaviour can still be overriden by passing a ``get_default`` function:: def another_default(component, module=None, **data): return component.__name__.lower() name.bind(get_default=another_default).get(some_component) Making the default behaviour intrinsic to the directive, prevents having to pass the ``get_default`` function over and over when getting values, for example in the grokkers. 0.13 (2010-11-01) ================= Feature changes --------------- * Ignore all __main__ modules. * List zope.testing as a test dependency. 0.12 (2009-06-29) ================= Feature changes --------------- * Changes to better support various inheritance scenarios in combination with directives. Details follow. * ``CLASS_OR_MODULE`` scope directives will be aware of inheritance of values that are defined in module-scope. Consider the following case:: module a: some_directive('A') class Foo(object): pass module b: import a class Bar(a.Foo): pass As before, ``Foo`` will have the value ``A`` configured for it. ``Bar``, since it inherits from ``Foo``, will inherit this value. * ``CLASS_OR_MODULE`` and ``CLASS`` scope directives will be aware of inheritance of computed default values. Consider the following case:: module a: class Foo(object): pass module b: import a class Bar(a.Foo): pass def get_default(component, module, **data): if module.__name__ == 'a': return "we have a default value for module a" return martian.UNKNOWN When we now do this:: some_directive.bind(get_default=get_default).get(b.Bar) We will get the value "we have a default value for module a". This is because when trying to compute the default value for ``Bar`` we returned ``martian.UNKNOWN`` to indicate the value couldn't be found yet. The system then looks at the base class and tries again, and in this case it succeeds (as the module-name is ``a``). * ``martian.ONCE_IFACE`` storage option to allow the creation of directives that store their value on ``zope.interface`` interfaces. This was originally in ``grokcore.view`` but was of wider usefulness. Bugs fixed ---------- * Ignore things that look like Python modules and packages but aren't. These are sometimes created by editors, operating systems and network file systems and we don't want to confuse them. * Ignore .pyc and .pyo files that don't have a matching .py file via ``module_info_from_dotted_name`` if its ``ignore_nonsource`` parameter is ``True``. The default is ``True``. To revert to the older behavior where .pyc files were honored, pass ``ignore_nonsource=False``. * Pass along ``exclude_filter`` (and the new ``ignore_nonsource`` flag) to ModuleInfo constructor when it calls itself recursively. * Replace ``fake_import`` to import fake modules in tests with a real python import statement (``from martiantest.fake import my_fake_module``). This works by introducing a metaclass for ``FakeModule`` that automatically registers it as a module. The irony does not escape us. This also means that ``martian.scan.resolve()`` will now work on fake modules. 0.11 (2008-09-24) ================= Feature changes --------------- * Added MULTIPLE_NOBASE option for directive store. This is like MULTIPLE but doesn't inherit information from the base class. 0.10 (2008-06-06) ================= Feature changes --------------- * Add a ``validateClass`` validate function for directives. * Moved ``FakeModule`` and ``fake_import`` into a ``martian.testing`` module so that they can be reused by external packages. * Introduce new tutorial text as README.txt. The text previously in ``README.txt`` was rather too detailed for a tutorial, so has been moved into ``core.txt``. * Introduce a ``GrokkerRegistry`` class that is a ``ModuleGrokker`` with a ``MetaMultiGrokker`` in it. This is the convenient thing to instantiate to start working with Grok and is demonstrated in the tutorial. * Introduced three new martian-specific directives: ``martian.component``, ``martian.directive`` and ``martian.priority``. These replace the ``component_class``, ``directives`` and ``priority`` class-level attributes. This way Grokkers look the same as what they grok. This breaks backwards compatibility again, but it's an easy replace operation. Note that ``martian.directive`` takes the directive itself as an argument, and then optionally the same arguments as the ``bind`` method of directives (``name``, ``default`` and ``get_default``). It may be used multiple times. Note that ``martian.baseclass`` was already a Martian-specific directive and this has been unchanged. * For symmetry, add an ``execute`` method to ``InstanceGrokker``. 0.9.7 (2008-05-29) ================== Feature changes --------------- * Added a ``MethodGrokker`` base class for grokkers that want to grok methods of a class rather than the whole class itself. It works quite similar to the ``ClassGrokker`` regarding directive definition, except that directives evaluated not only on class (and possibly module) level but also for each method. That way, directives can also be applied to methods (as decorators) in case they support it. 0.9.6 (2008-05-14) ================== Feature changes --------------- * Refactored the ``martian.Directive`` base class yet again to allow more declarative (rather than imperative) usage in grokkers. Directives themselves no longer have a ``get()`` method nor a default value factory (``get_default()``). Instead you will have to "bind" the directive first which is typically done in a grokker. * Extended the ``ClassGrokker`` baseclass with a standard ``grok()`` method that allows you to simply declare a set of directives that are used on the grokked classes. Then you just have to implement an ``execute()`` method that will receive the data from those directives as keyword arguments. This simplifies the implementation of class grokkers a lot. 0.9.5 (2008-05-04) ================== * ``scan_for_classes`` just needs a single second argument specifying an interface. The support for scanning for subclasses directly has been removed as it became unnecessary (due to changes in grokcore.component). 0.9.4 (2008-05-04) ================== Features changes ---------------- * Replaced the various directive base classes with a single ``martian.Directive`` base class: - The directive scope is now defined with the ``scope`` class attribute using one of ``martian.CLASS``, ``martian.MODULE``, ``martian.CLASS_OR_MODULE``. - The type of storage is defined with the ``store`` class attribute using one of ``martian.ONCE``, ``martian.MULTIPLE``, ``martian.DICT``. - Directives have now gained the ability to read the value that they have set on a component or module using a ``get()`` method. The ``class_annotation`` and ``class_annotation_list`` helpers have been removed as a consequence. * Moved the ``baseclass()`` directive from Grok to Martian. * Added a ``martian.util.check_provides_one`` helper, in analogy to ``check_implements_one``. * The ``scan_for_classes`` helper now also accepts an ``interface`` argument which allows you to scan for classes based on interface rather than base classes. Bug fixes --------- * added dummy ``package_dotted_name`` to ``BuiltinModuleInfo``. This allows the grokking of views in test code using Grok's ``grok.testing.grok_component`` without a failure when it sets up the ``static`` attribute. * no longer use the convention that classes ending in -Base will be considered base classes. You must now explicitly use the grok.baseclass() directive. * The type check of classes uses isinstance() instead of type(). This means Grok can work with Zope 2 ExtensionClasses and metaclass programming. 0.9.3 (2008-01-26) ================== Feature changes --------------- * Added an OptionalValueDirective which allows the construction of directives that take either zero or one argument. If no arguments are given, the ``default_value`` method on the directive is called. Subclasses need to override this to return the default value to use. Restructuring ------------- * Move some util functions that were really grok-specific out of Martian back into Grok. 0.9.2 (2007-11-20) ================== Bug fixes --------- * scan.module_info_from_dotted_name() now has special behavior when it runs into __builtin__. Previously, it would crash with an error. Now it will return an instance of BuiltinModuleInfo. This is a very simple implementation which provides just enough information to make client code work. Typically this client code is test-related so that the module context will be __builtin__. 0.9.1 (2007-10-30) ================== Feature changes --------------- * Grokkers now receive a ``module_info`` keyword argument. This change is completely backwards-compatible since grokkers which don't take ``module_info`` explicitly will absorb the extra argument in ``**kw``. 0.9 (2007-10-02) ================= Feature changes --------------- * Reverted the behaviour where modules called tests or ftests were skipped by default and added an API to provides a filtering function for skipping modules to be grokked. 0.8.1 (2007-08-13) ================== Feature changes --------------- * Don't grok tests or ftests modules. Bugs fixed ---------- * Fix a bug where if a class had multiple base classes, this could end up in the resultant list multiple times. 0.8 (2007-07-02) ================ Feature changes --------------- * Initial public release. martian-0.14/setup.cfg0000644000175000017500000000007311464246723012545 0ustar jwjw[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 martian-0.14/LICENSE.txt0000644000175000017500000000402611464246706012552 0ustar jwjwZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). 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. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. 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. martian-0.14/setup.py0000644000175000017500000000204011464246706012433 0ustar jwjwimport os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() long_description = ( read('README.txt') + '\n' + read('src', 'martian', 'README.txt') + '\n' + read('CHANGES.txt') + '\n' + 'Download\n' '********\n' ) setup( name='martian', version='0.14', author='Grok project', author_email='grok-dev@zope.org', description="""\ Martian is a library that allows the embedding of configuration information in Python code. Martian can then grok the system and do the appropriate configuration registrations. One example of a system that uses Martian is the system where it originated: Grok (http://grok.zope.org) """, long_description=long_description, packages=find_packages('src'), package_dir = {'': 'src'}, include_package_data = True, zip_safe=False, license='ZPL', install_requires=[ 'zope.interface', 'setuptools', ], extras_require = dict(test=['zope.testing']), ) martian-0.14/bootstrap.py0000644000175000017500000002336611464246706013326 0ustar jwjw############################################################################## # # Copyright (c) 2006 Zope Foundation 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. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess from optparse import OptionParser if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: quote = str # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. stdout, stderr = subprocess.Popen( [sys.executable, '-Sc', 'try:\n' ' import ConfigParser\n' 'except ImportError:\n' ' print 1\n' 'else:\n' ' print 0\n'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() has_broken_dash_S = bool(int(stdout.strip())) # In order to be more robust in the face of system Pythons, we want to # run without site-packages loaded. This is somewhat tricky, in # particular because Python 2.6's distutils imports site, so starting # with the -S flag is not sufficient. However, we'll start with that: if not has_broken_dash_S and 'site' in sys.modules: # We will restart with python -S. args = sys.argv[:] args[0:0] = [sys.executable, '-S'] args = map(quote, args) os.execv(sys.executable, args) # Now we are running with -S. We'll get the clean sys.path, import site # because distutils will do it later, and then reset the path and clean # out any namespace packages from site-packages that might have been # loaded by .pth files. clean_path = sys.path[:] import site sys.path[:] = clean_path for k, v in sys.modules.items(): if (hasattr(v, '__path__') and len(v.__path__)==1 and not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))): # This is a namespace package. Remove it. sys.modules.pop(k) is_jython = sys.platform.startswith('java') setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' distribute_source = 'http://python-distribute.org/distribute_setup.py' # parsing arguments def normalize_to_url(option, opt_str, value, parser): if value: if '://' not in value: # It doesn't smell like a URL. value = 'file://%s' % ( urllib.pathname2url( os.path.abspath(os.path.expanduser(value))),) if opt_str == '--download-base' and not value.endswith('/'): # Download base needs a trailing slash to make the world happy. value += '/' else: value = None name = opt_str[2:].replace('-', '_') setattr(parser.values, name, value) usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --setup-source and --download-base to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="use_distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("--setup-source", action="callback", dest="setup_source", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or file location for the setup file. " "If you use Setuptools, this will default to " + setuptools_source + "; if you use Distribute, this " "will default to " + distribute_source +".")) parser.add_option("--download-base", action="callback", dest="download_base", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or directory for downloading " "zc.buildout and either Setuptools or Distribute. " "Defaults to PyPI.")) parser.add_option("--eggs", help=("Specify a directory for storing eggs. Defaults to " "a temporary directory that is deleted when the " "bootstrap script completes.")) parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout's main function if options.config_file is not None: args += ['-c', options.config_file] if options.eggs: eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) else: eggs_dir = tempfile.mkdtemp() if options.setup_source is None: if options.use_distribute: options.setup_source = distribute_source else: options.setup_source = setuptools_source if options.accept_buildout_test_releases: args.append('buildout:accept-buildout-test-releases=true') args.append('bootstrap') try: import pkg_resources import setuptools # A flag. Sometimes pkg_resources is installed alone. if not hasattr(pkg_resources, '_distribute'): raise ImportError except ImportError: ez_code = urllib2.urlopen( options.setup_source).read().replace('\r\n', '\n') ez = {} exec ez_code in ez setup_args = dict(to_dir=eggs_dir, download_delay=0) if options.download_base: setup_args['download_base'] = options.download_base if options.use_distribute: setup_args['no_fake'] = True ez['use_setuptools'](**setup_args) reload(sys.modules['pkg_resources']) import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) cmd = [quote(sys.executable), '-c', quote('from setuptools.command.easy_install import main; main()'), '-mqNxd', quote(eggs_dir)] if not has_broken_dash_S: cmd.insert(1, '-S') find_links = options.download_base if not find_links: find_links = os.environ.get('bootstrap-testing-find-links') if find_links: cmd.extend(['-f', quote(find_links)]) if options.use_distribute: setup_requirement = 'distribute' else: setup_requirement = 'setuptools' ws = pkg_resources.working_set setup_requirement_path = ws.find( pkg_resources.Requirement.parse(setup_requirement)).location env = dict( os.environ, PYTHONPATH=setup_requirement_path) requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setup_requirement_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) if is_jython: import subprocess exitcode = subprocess.Popen(cmd, env=env).wait() else: # Windows prefers this, apparently; otherwise we would prefer subprocess exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) if exitcode != 0: sys.stdout.flush() sys.stderr.flush() print ("An error occurred when trying to install zc.buildout. " "Look above this message for any errors that " "were output by easy_install.") sys.exit(exitcode) ws.add_entry(eggs_dir) ws.require(requirement) import zc.buildout.buildout zc.buildout.buildout.main(args) if not options.eggs: # clean up temporary egg directory shutil.rmtree(eggs_dir) martian-0.14/README.txt0000644000175000017500000000011311464246706012416 0ustar jwjw******* Martian ******* A library to grok configuration from Python code. martian-0.14/src/0000755000175000017500000000000011464246723011513 5ustar jwjwmartian-0.14/src/martian/0000755000175000017500000000000011464246723013146 5ustar jwjwmartian-0.14/src/martian/scan.txt0000644000175000017500000002056711464246706014646 0ustar jwjwScanning modules ================ Martian can grok modules or packages. In order to grok packages, it needs to scan all modules in it. The martian.scan package provides an abstraction over packages and modules that helps with the scanning process. >>> from martian.scan import module_info_from_dotted_name We have provided a special test fixture package called stoneage that we are going to scan, in ``martian.tests.stoneage``. Modules ------- The scanning module defines a class ``ModuleInfo`` that provides information about a module or a package. Let's take a look at the ``cave`` module in the stone-age package:: >>> module_info = module_info_from_dotted_name('martian.tests.stoneage.cave') We get a ``ModuleInfo`` object representing the ``cave module:: >>> module_info ``cave`` is a module, not a package. >>> module_info.isPackage() False We can retrieve the name of the module:: >>> module_info.name 'cave' We can also retrieve the dotted name of the module:: >>> module_info.dotted_name 'martian.tests.stoneage.cave' And the dotted name of the package the module is in:: >>> module_info.package_dotted_name 'martian.tests.stoneage' It is possible to get the actual module object that the ModuleInfo object stands for, in this case the package's ``cave.py``:: >>> module = module_info.getModule() >>> module We can store a module-level annotation in the module:: >>> module.__grok_foobar__ = 'GROK LOVE FOO' The ModuleInfo object allows us to retrieve the annotation again:: >>> module_info.getAnnotation('grok.foobar', None) 'GROK LOVE FOO' If a requested annotation does not exist, we get the default value:: >>> module_info.getAnnotation('grok.barfoo', 42) 42 A module has no sub-modules in it (only packages have this):: >>> module_info.getSubModuleInfos() [] Trying to retrieve any sub modules will give back None:: >>> print module_info.getSubModuleInfo('doesnotexist') None Packages -------- Now let's scan a package:: >>> module_info = module_info_from_dotted_name('martian.tests.stoneage') We will get a ModuleInfo instance representing the ``stoneage`` package:: >>> module_info The object knows it is a package:: >>> module_info.isPackage() True Like with the module, we can get the package's name:: >>> module_info.name 'stoneage' We can also get the package's dotted name back from it:: >>> module_info.dotted_name 'martian.tests.stoneage' It is also possible to get the dotted name of the nearest package the package resides in. This will always be itself:: >>> module_info.package_dotted_name 'martian.tests.stoneage' Now let's go into the package and a few sub modules that are in it:: >>> module_info.getSubModuleInfo('cave') >>> module_info.getSubModuleInfo('hunt') Trying to retrieve non-existing sub modules gives back None:: >>> print module_info.getSubModuleInfo('doesnotexist') None It is possible to get the actual module object that the ModuleInfo object stands for, in this case the package's ``__init__.py``:: >>> module = module_info.getModule() >>> module A package has sub modules:: >>> sub_modules = module_info.getSubModuleInfos() >>> sub_modules [, , ] Resource paths -------------- Resources can be stored in a directory alongside a module (in their containing package). We can get the path to such a resource directory using the ``getResourcePath`` method. For packages, a resource path will be a child of the package directory: >>> import os.path >>> expected_resource_path = os.path.join(os.path.dirname( ... module.__file__), 'stoneage-templates') >>> resource_path = module_info.getResourcePath('stoneage-templates') >>> resource_path == expected_resource_path True For modules, a resource path will be a sibling of the module's file: >>> cave_module_info = module_info_from_dotted_name( ... 'martian.tests.stoneage.cave') >>> expected_resource_path = os.path.join(os.path.dirname( ... cave_module_info.getModule().__file__), 'cave-templates') >>> resource_path = cave_module_info.getResourcePath('cave-templates') >>> resource_path == expected_resource_path True Skipping packages and modules ----------------------------- By default no packages and modules are skipped from the grokking procedure to guarantee a generic behaviour:: >>> from martian.scan import ModuleInfo, module_info_from_dotted_name >>> module_info = module_info_from_dotted_name( ... 'martian.tests.withtestspackages') >>> module_info >>> # *Will* contain the module info for the tests and ftests packages >>> print module_info.getSubModuleInfos() [......] You can, however, tell ``getSubmoduleInfos()`` to skip certain names of packages and modules. To do that, you have to give a filter function which takes a name and returns a boolean. Names, for which the function returns ``True`` are skipped from the result. For example, to get only those packages, which are *not* named 'tests' nor 'ftests' we could do:: >>> from martian.scan import ModuleInfo, module_info_from_dotted_name >>> no_tests_filter = lambda x: x in ['tests', 'ftests'] >>> module_info = module_info_from_dotted_name( ... 'martian.tests.withtestsmodules', exclude_filter=no_tests_filter) >>> module_info >>> no_tests_filter = lambda x: x in ['tests', 'ftests'] >>> print module_info.getSubModuleInfos() [] By default __main__ packages are always ignored:: >>> module_info = module_info_from_dotted_name( ... 'martian.tests.with__main__') >>> print module_info.getSubModuleInfos() [] Non-modules that look like modules ---------------------------------- Sometimes the environment (an editor or network file system, for instance) will create a situation where there is a file or directory that looks like a Python module or package but is in fact not really one (file name starts with a dot, for instance). Module and package names must be valid Python identifiers. The package ``martian.tests.withbogusmodules`` contains only one real module and one real package. The rest are almost right, but do not start with an underscore and a letter and are therefore not valid. We will see that Martian will ignore these other things:: >>> module_info = module_info_from_dotted_name( ... 'martian.tests.withbogusmodules') >>> module_info.getSubModuleInfos() [, ] Packages which contain .pyc files only -------------------------------------- When a .py file in a package is renamed, an "orphaned" .pyc object is often left around. By default, Martian will ignore such .pyc files: >>> import martian.tests.withpyconly >>> from martian.tests.withpyconly import foo >>> d = os.path.abspath(os.path.dirname(martian.tests.withpyconly.__file__)) >>> os.rename(os.path.join(d, 'foo.py'), os.path.join(d, 'foo.py_aside')) >>> module_info = module_info_from_dotted_name( ... 'martian.tests.withpyconly') >>> module_info.getSubModuleInfos() [] However, if ``ignore_nonsource=False`` is passed to ``module_info_from_dotted_name`` (or friends, such as ``module_info_from_module``), we will pick up these modules:: >>> module_info = module_info_from_dotted_name( ... 'martian.tests.withpyconly', ignore_nonsource=False) >>> module_info.getSubModuleInfos() [, ] >>> # rename back to normal name >>> os.rename(os.path.join(d, 'foo.py_aside'), os.path.join(d, 'foo.py')) martian-0.14/src/martian/directive.txt0000644000175000017500000010361511464246706015674 0ustar jwjwDirectives ========== When grokking a class, the grokking procedure can be informed by directives, on a class, or a module. If a directive is absent, the system falls back to a default. Here we introduce a general way to define these directives, and how to use them to retrieve information for a class for use during the grokking procedure. A simple directive ------------------ We define a simple directive that sets a description:: >>> from martian import Directive, CLASS, ONCE >>> class description(Directive): ... scope = CLASS ... store = ONCE ... default = u'' The name of the directive is ``description``. We specify that the directive can only be used in the scope of a class. We also specify it can only be used a single time. Finally we define the default in case the directive is absent (the empty string). Now we look at the directive in action:: >>> class Foo(object): ... description(u"This is a description") After setting the description, we bind the directive and obtain a bound directive object. This object has means for retrieving the data set by the directive, in particular the ``get`` method:: >>> description.bind().get(Foo) u'This is a description' Directives in different namespaces get stored differently. We'll define a similar directive in another namespace:: >>> class description2(description): ... pass >>> class Foo(object): ... description(u"Description1") ... description2(u"Description2") >>> description.bind().get(Foo) u'Description1' >>> description2.bind().get(Foo) u'Description2' If we check the value of a class without the directive, we see the default value for that directive, this case the empty unicode string:: >>> class Foo(object): ... pass >>> description.bind().get(Foo) u'' In certain cases we need to set a value on a component as if the directive was actually used:: >>> description.set(Foo, u'value as set') >>> description.bind().get(Foo) u'value as set' Subclasses of the original class will inherit the properties set by the directive: >>> class Foo(object): ... description('This is a foo.') ... >>> class Bar(Foo): ... pass ... >>> description.bind().get(Bar) 'This is a foo.' When we use the directive outside of class scope, we get an error message:: >>> description('Description') Traceback (most recent call last): ... GrokImportError: The 'description' directive can only be used on class level. In particular, we cannot use it in a module:: >>> class testmodule(FakeModule): ... fake_module = True ... description("Description") Traceback (most recent call last): ... GrokImportError: The 'description' directive can only be used on class level. We cannot use the directive twice in the class scope. If we do so, we get an error message as well:: >>> class Foo(object): ... description(u"Description1") ... description(u"Description2") Traceback (most recent call last): ... GrokImportError: The 'description' directive can only be called once per class. We cannot call the directive with no argument either:: >>> class Foo(object): ... description() Traceback (most recent call last): ... TypeError: description takes exactly 1 argument (0 given) Class and module scope ---------------------- We define a ``layer`` directive that can be used in class and module scope both:: >>> from martian import CLASS_OR_MODULE >>> class layer(Directive): ... scope = CLASS_OR_MODULE ... store = ONCE By default, the ``default`` property is None which is why we can omit specifying it here. This directive has been declared ``CLASS_OR_MODULE``, so you will always have to pass a module to the directive. Since we don't have a module yet we'll simply create a dummy, empty, fallback module:: >>> dummy = object() We can use this directive now on a class:: >>> class Foo(object): ... layer('Test') >>> layer.bind().get(Foo, dummy) 'Test' The defaulting to ``None`` works:: >>> class Foo(object): ... pass >>> layer.bind().get(Foo, dummy) is None True We can also use it in a module:: >>> class testmodule(FakeModule): ... layer('Test2') ... class Foo(object): ... pass >>> from martiantest.fake import testmodule When we now try to access ``layer`` on ``Foo``, we find the module-level default which we just set. We pass the module as the second argument to the ``get`` method to have it fall back on this:: >>> layer.bind().get(testmodule.Foo, testmodule) 'Test2' Let's look at a module where the directive is not used:: >>> class testmodule(FakeModule): ... class Foo(object): ... pass >>> from martiantest.fake import testmodule In this case, the value cannot be found so the system falls back on the default, ``None``:: >>> layer.bind().get(testmodule.Foo, testmodule) is None True Like with CLASS scope directive where values set are inherited by subclasses, values set on a class or module level are inherited too, even if the subclass is defined another module:: >>> class testmodule_a(FakeModule): ... layer('Value set on baseclass module') ... class FooA(object): ... pass >>> from martiantest.fake import testmodule_a >>> >>> class testmodule_b(FakeModule): ... class FooB(testmodule_a.FooA): ... pass >>> from martiantest.fake import testmodule_b On the baseclass:: >>> layer.bind().get(testmodule_a.FooA) 'Value set on baseclass module' Inherited by the subclass:: >>> layer.bind().get(testmodule_b.FooB) 'Value set on baseclass module' Whenever there's a directive set on the baseclass' module, it will take precedence like with "normal" inheritance:: >>> class testmodule_c(FakeModule): ... layer('Value set on subclass module') ... class FooC(testmodule_a.FooA): ... pass >>> from martiantest.fake import testmodule_c >>> layer.bind().get(testmodule_c.FooC) 'Value set on subclass module' Let's now look at this using a directive with CLASS scope only:: >>> class layer2(Directive): ... scope = CLASS ... store = ONCE Inheritance in combination with module scope -------------------------------------------- Let's look at how our layer directive can inherit in combination with directive use on module scope. First we define a module which defines a class that gets the ``Test`` layer through the use of the layer directive on module scope:: >>> class inheritmodule1(FakeModule): ... layer('Test') ... class Foo(object): ... pass >>> from martiantest.fake import inheritmodule1 Now we define another module that has a class that inherits from ``Foo``:: >>> class inheritmodule2(FakeModule): ... class Bar(inheritmodule1.Foo): ... pass >>> from martiantest.fake import inheritmodule2 We will now see that ``Bar`` has inherited the layer ``Test``:: >>> layer.bind().get(inheritmodule2.Bar, inheritmodule2) 'Test' Let's try it with another level of inheritance:: >>> class inheritmodule3(FakeModule): ... class Baz(inheritmodule2.Bar): ... pass >>> from martiantest.fake import inheritmodule3 The layer should still be inherited:: >>> layer.bind().get(inheritmodule3.Baz, inheritmodule3) 'Test' Let's override now by having an explicit layer directive on the class that subclasses ``Foo``:: >>> class inheritmodule4(FakeModule): ... class OverrideFoo(inheritmodule1.Foo): ... layer('AnotherTest') >>> from martiantest.fake import inheritmodule4 >>> layer.bind().get(inheritmodule4.OverrideFoo, inheritmodule4) 'AnotherTest' Let's override now by having an explicit layer directive on module-level instead:: >>> class inheritmodule5(FakeModule): ... layer('AnotherTest') ... class OverrideFoo(inheritmodule1.Foo): ... pass >>> from martiantest.fake import inheritmodule5 >>> layer.bind().get(inheritmodule5.OverrideFoo, inheritmodule5) 'AnotherTest' Inheritance with module scope also works for old-style classes:: >>> class oldstyle1(FakeModule): ... layer('one') ... class Base: ... pass >>> from martiantest.fake import oldstyle1 >>> class oldstyle2(FakeModule): ... class Sub(oldstyle1.Base): ... pass >>> from martiantest.fake import oldstyle2 >>> layer.bind().get(oldstyle2.Sub) 'one' Using a directive multiple times -------------------------------- A directive can be configured to allow it to be called multiple times in the same scope:: >>> from martian import MultipleTimesDirective >>> class multi(MultipleTimesDirective): ... scope = CLASS We can now use the directive multiple times without any errors:: >>> class Foo(object): ... multi(u"Once") ... multi(u"Twice") We can now retrieve the value and we'll get a list:: >>> multi.bind().get(Foo) [u'Once', u'Twice'] The default value for a MultipleTimesDirective is an empty list:: >>> class Bar(object): ... pass >>> multi.bind().get(Bar) [] Whenever the directive is used on a sub class of a component, the values set by directives on the base classes are combined:: >>> class Qux(Foo): ... multi(u'Triple') ... >>> multi.bind().get(Qux) [u'Once', u'Twice', u'Triple'] You can also create a directive that ignores the values on the base classes:: >>> from martian import MULTIPLE_NOBASE >>> class multi(Directive): ... scope = CLASS ... store = MULTIPLE_NOBASE >>> class Foo(object): ... multi(u"Once") ... multi(u"Twice") >>> multi.bind().get(Foo) [u'Once', u'Twice'] >>> class Qux(Foo): ... multi(u'Triple') ... multi(u'More') >>> multi.bind().get(Qux) [u'Triple', u'More'] Using a directive multiple times, as a dictionary ------------------------------------------------- A directive can be configured to allow it to be called multiple times in the same scope. In this case the factory method should be overridden to return a key-value pair:: >>> from martian import DICT >>> class multi(Directive): ... scope = CLASS ... store = DICT ... def factory(self, value): ... return value.lower(), value We can now use the directive multiple times without any errors:: >>> class Bar(object): ... multi(u"Once") ... multi(u"Twice") We can now retrieve the value and we'll get a to the items:: >>> d = multi.bind().get(Bar) >>> print sorted(d.items()) [(u'once', u'Once'), (u'twice', u'Twice')] When the factory method does not return a key-value pair, an error is raised:: >>> class wrongmulti(Directive): ... scope = CLASS ... store = DICT ... def factory(self, value): ... return None >>> class Baz(object): ... wrongmulti(u"Once") Traceback (most recent call last): ... GrokImportError: The factory method for the 'wrongmulti' directive should return a key-value pair. >>> class wrongmulti2(Directive): ... scope = CLASS ... store = DICT ... def factory(self, value): ... return value, value, value >>> class Baz(object): ... wrongmulti2(u"Once") Traceback (most recent call last): ... GrokImportError: The factory method for the 'wrongmulti2' directive should return a key-value pair. Like with MULTIPLE store, values set by directives using the DICT store are combined:: >>> class multi(Directive): ... scope = CLASS ... store = DICT ... def factory(self, value, andanother): ... return value, andanother ... >>> class Frepple(object): ... multi(1, 'AAA') ... multi(2, 'BBB') ... >>> class Fropple(Frepple): ... multi(1, 'CCC') ... multi(3, 'DDD') ... multi(4, 'EEE') >>> d = multi.bind().get(Fropple) >>> print sorted(d.items()) [(1, 'CCC'), (2, 'BBB'), (3, 'DDD'), (4, 'EEE')] Using MULTIPLE and DICT can also work on a module level, even though inheritance has no meaning there:: >>> from martian import MODULE >>> class multi(MultipleTimesDirective): ... scope = MODULE ... >>> multi.__module__ = 'somethingelse' >>> class module_with_directive(FakeModule): ... fake_module = True ... ... multi('One') ... multi('Two') ... >>> from martiantest.fake import module_with_directive >>> print multi.bind().get(module_with_directive) ['One', 'Two'] >>> from martian import MODULE >>> class multi(Directive): ... scope = MODULE ... store = DICT ... def factory(self, value, andanother): ... return value, andanother ... >>> multi.__module__ = 'somethingelse' >>> class module_with_directive(FakeModule): ... fake_module = True ... ... multi(1, 'One') ... multi(2, 'Two') ... >>> from martiantest.fake import module_with_directive >>> d = multi.bind().get(module_with_directive) >>> print sorted(d.items()) [(1, 'One'), (2, 'Two')] Directives on an interface -------------------------- When you use ``zope.interface.Interface`` to define a new interface using the class statement, in fact a special interface instance is created, not a class. To let the directive store a value on an interface, we need to use a special storage (``martian.ONCE_IFACE``):: >>> from martian import ONCE_IFACE >>> class skin(Directive): ... scope = CLASS ... store = ONCE_IFACE Note that we still indicate the ``CLASS`` scope for this kind of directive. At some point we may introduce a special scope for directives on interfaces. Let's try the directive. We shouldn't get an error:: >>> class once_iface(FakeModule): ... from zope.interface import Interface ... class TestIface(Interface): ... skin('Foo') >>> from martiantest.fake import once_iface We can now retrieve the value:: >>> skin.bind().get(once_iface.TestIface) 'Foo' Computed defaults ----------------- Often instead of just supplying the system with a default, we want to compute the default in some way. The computation of the default can be defined in the directive class, by the ``get_default`` classmethod. We define the ``name`` directive, which if not present, will compute its value from the name of class, lower-cased. >>> class name(Directive): ... scope = CLASS ... store = ONCE ... ... @classmethod ... def get_default(cls, component, module=None, **data): ... return component.__name__.lower() ... >>> bound_name = name.bind() >>> >>> class Foo(object): ... name('bar') ... >>> bound_name.get(Foo) 'bar' >>> class Foo2(object): ... pass ... >>> bound_name.get(Foo2) 'foo2' To override the default-default behaviour you can pass a function when binding the directive. In this case, we do not want the default behaviour of using the lower cased class name, but have it upper cased:: >>> def default_name_uppercase(component, module, **data): ... return component.__name__.upper() ... >>> bound_name = name.bind(get_default=default_name_uppercase) >>> class Foo(object): ... name('bar') >>> bound_name.get(Foo) 'bar' >>> class Foo2(object): ... pass >>> bound_name.get(Foo2) 'FOO2' Let's test this with a deeper inheritance hierarchy. Explicit directives should always trump computed defaults:: >>> class Subclass(Foo): ... pass >>> bound_name.get(Subclass) 'bar' Now let's look at a hierarchy in which the explicit rule should apply:: >>> bound_name2 = name.bind() >>> class Alpha(object): ... pass >>> class Beta(Alpha): ... pass >>> bound_name2.get(Alpha) 'alpha' >>> bound_name2.get(Beta) 'beta' We will now define a default rule that only triggers for particular components in the inheritance chain, but returns ``UNKNOWN`` otherwise. This can be useful if the default rule is dependent on outside information. In Grok for instance, the default rule for ``grok.context`` will look for a class that implements ``IContext`` in the same module, and ``grok.templatedir`` will look for a directory with templates with a name based on the name of the module with ``_templates`` appended. This rule returns a value only if the module name includes the number ``1``, and will return ``UNKNOWN`` otherwise:: >>> import martian >>> def default_name_lowercase3(component, module, **data): ... if '1' in module.__name__: ... return component.__name__.lower() ... return martian.UNKNOWN >>> bound_name3 = name.bind(get_default=default_name_lowercase3) This won't trigger for this module, as it doesn't have the character ``1`` in it:: >>> class testmodule(FakeModule): ... class Foo(object): ... pass >>> from martiantest.fake import testmodule >>> bound_name3.get(testmodule.Foo, testmodule) is martian.UNKNOWN True Now we define a module which does have ``1`` in it, so the rule should be triggered:: >>> class testmodule1(FakeModule): ... class Foo(object): ... pass >>> from martiantest.fake import testmodule1 >>> bound_name3.get(testmodule1.Foo, testmodule1) 'foo' This also works with inheritance:: >>> class testmodule2(FakeModule): ... class Bar(testmodule1.Foo): ... pass >>> from martiantest.fake import testmodule2 >>> bound_name3.get(testmodule2.Bar, testmodule2) 'foo' Module-level explicit directives always trump computed defaults as well. The ``layer`` directive is ``CLASS_OR_MODULE`` scope. Let's set up a hierarchy of modules and classes using ``layer`` to demonstrate this:: >>> class inheritmodule1(FakeModule): ... layer('Test') ... class Foo(object): ... pass >>> from martiantest.fake import inheritmodule1 >>> class inheritmodule2(FakeModule): ... class Bar(inheritmodule1.Foo): ... pass >>> from martiantest.fake import inheritmodule2 We define a way to compute the default in which we compute a string based on the module name and the class name, so we can later check whether the right module and class was passed to compute the default:: >>> def immediate_get_default(component, module, **data): ... return "computed: %s %s" % (module.__name__, component.__name__) We don't expect the default rule to kick in as we can find an explicitly set value:: >>> layer.bind(get_default=immediate_get_default).get(inheritmodule2.Bar, ... inheritmodule2) 'Test' Let's now consider a case where we have inheritance without explicit use of the ``layer`` directive:: >>> class inheritmodule1(FakeModule): ... class Foo(object): ... pass >>> from martiantest.fake import inheritmodule1 >>> class inheritmodule2(FakeModule): ... class Bar(inheritmodule1.Foo): ... pass >>> from martiantest.fake import inheritmodule2 We expect to receive the computed default for ``Bar``, as ``immediate_get_default`` immediately returns a result for any component:: >>> layer.bind(get_default=immediate_get_default).get(inheritmodule2.Bar, ... inheritmodule2) 'computed: martiantest.fake.inheritmodule2 Bar' Let's try the default rule that triggers upon seeing ``1`` in the module name again, this time for the ``CLASS_OR_MODULE`` scope directive ``layer``:: >>> def picky_get_default(component, module, **data): ... if '1' in module.__name__: ... return "computed: %s %s" % (module.__name__, component.__name__) ... return martian.UNKNOWN Since only the ``Foo`` class is in a module with the character ``1`` in it (``inheritmodule1``), we will get the result for ``Foo`` (and its module):: >>> layer.bind(get_default=picky_get_default).get(inheritmodule2.Bar, ... inheritmodule2) 'computed: martiantest.fake.inheritmodule1 Foo' We will get the same result if we ask ``Foo`` directly:: >>> layer.bind(get_default=picky_get_default).get(inheritmodule1.Foo, ... inheritmodule1) 'computed: martiantest.fake.inheritmodule1 Foo' If we have a hierarchy that never has a module with the character ``1`` in it, we will receive ``UNKNOWN`` (and the grokkker that uses this directive should raise an error):: >>> class inheritmodule3(FakeModule): ... class Foo(object): ... pass >>> from martiantest.fake import inheritmodule3 >>> layer.bind(get_default=picky_get_default).get(inheritmodule3.Foo, ... inheritmodule3) is martian.UNKNOWN True Raising errors in a computed default ------------------------------------ Let's define a simple directive with a default rule that always raises a ``GrokError`` if the class name doesn't start with an upper case letter:: >>> from martian.error import GrokError >>> class name(Directive): ... scope = CLASS ... store = ONCE ... ... @classmethod ... def get_default(cls, component, module, **data): ... if component.__name__[0].isupper(): ... return component.__name__ ... raise GrokError( ... "Component %r has a name that doesn't start with upper " ... "case letter." % component, component) Let's test it:: >>> class A(object): ... pass >>> class b(object): ... pass >>> name.bind().get(A) 'A' >>> name.bind().get(b) Traceback (most recent call last): ... GrokError: Component has a name that doesn't start with upper case letter. Instead of raising ``GrokError`` we can also raise ``UnknownError`` in a computed default. This has the same meaning as returning ``UNKNOWN``, except that the error information is recorded and the default rule is tried again on the base class in the mro chain. If the default rule has the error raised or ``UNKNOWN`` value returned in each step of the chain, the first ``UnknownError`` that was raised is converted into a ``GrokError``. This makes it possible for the default logic to raise specific errors for the most specific class if the implicit rule failed to apply on the class and any of its bases. This is used for instance in the implementation of the ``get_default`` rule for ``grok.context``. Let's test this behavior with a rule that raises an ``UnknownError`` if there is no ``foo`` attribute on the class:: >>> from martian import UnknownError >>> class name(Directive): ... scope = CLASS ... store = ONCE ... ... @classmethod ... def get_default(cls, component, module, **data): ... if not component.__dict__.has_key('foo'): ... raise UnknownError( ... "Foo cannot be found for class %s" % component.__name__, ... component) ... return "Found for class %s" % component.__name__ Let's try it on a simple class first:: >>> class Test: ... pass >>> name.bind().get(Test) Traceback (most recent call last): ... GrokError: Foo cannot be found for class Test Let's try it on a new style class:: >>> class Test(object): ... pass >>> name.bind().get(Test) Traceback (most recent call last): ... GrokError: Foo cannot be found for class Test Let's try it on a class where there is some inheritance:: >>> class Test1(object): ... pass >>> class Test2(Test1): ... pass >>> name.bind().get(Test2) Traceback (most recent call last): ... GrokError: Foo cannot be found for class Test2 As you can see the error message will apply to the most specific class, ``Test2``, even though of course the error will also occur for the base class, ``Test1``. Let's now demonstrate an inheritance scenario where the error does not occur, because the get_default rule will succeed at some point during the inheritance chain:: >>> class Test1(object): ... foo = 1 >>> class Test2(Test1): ... pass >>> name.bind().get(Test2) 'Found for class Test1' Computed defaults for instances ------------------------------- In some cases directives are used to retrieve values from instances instead of from their classes:: >>> class name(Directive): ... scope = CLASS ... store = ONCE >>> class instancemodule(FakeModule): ... class Hoi(object): ... name('Test') ... class NoDirectiveOnThis(object): ... pass ... hoi = Hoi() ... no_directive_on_this = NoDirectiveOnThis() >>> from martiantest.fake import instancemodule Let's try to use the directive:: >>> name.bind().get(instancemodule.hoi, instancemodule) 'Test' If no directive was used on the class, we will get ``None``, the default default value:: >>> (name.bind().get(instancemodule.no_directive_on_this, instancemodule) is ... None) True Let's try it with a computed value now:: >>> def get_default(component, module, **data): ... return "The default" >>> name.bind(get_default=get_default).get(instancemodule.no_directive_on_this) 'The default' Computed default for old-style classes -------------------------------------- We should also test old-style classes with ``CLASS`` scope directives in combination with computed defaults:: >>> class layer2(Directive): ... scope = CLASS ... store = ONCE ... @classmethod ... def get_default(cls, component, module=None, **data): ... if '1' in module.__name__: ... return 'we found it' ... return martian.UNKNOWN ... >>> class oldstyle1(FakeModule): ... class Base: ... pass >>> from martiantest.fake import oldstyle1 >>> class oldstyle2(FakeModule): ... class Sub(oldstyle1.Base): ... pass >>> from martiantest.fake import oldstyle2 >>> layer2.bind().get(oldstyle2.Sub) 'we found it' And let's try it with a ``CLASS_OR_MODULE`` scope directive too:: >>> class layer3(Directive): ... scope = CLASS_OR_MODULE ... store = ONCE ... @classmethod ... def get_default(cls, component, module=None, **data): ... if '1' in module.__name__: ... return 'we found it' ... return martian.UNKNOWN >>> layer3.bind().get(oldstyle2.Sub) 'we found it' A marker directive ------------------ Another type of directive is a marker directive. This directive takes no arguments at all, but when used it marks the context:: >>> from martian import MarkerDirective >>> class mark(MarkerDirective): ... scope = CLASS >>> class Foo(object): ... mark() Class ``Foo`` is now marked:: >>> mark.bind().get(Foo) True When we have a class that isn't marked, we get the default value, ``False``:: >>> class Bar(object): ... pass >>> mark.bind().get(Bar) False If we pass in an argument, we get an error:: >>> class Bar(object): ... mark("An argument") Traceback (most recent call last): ... TypeError: mark takes no arguments (1 given) Validation ---------- A directive can be supplied with a validation method. The validation method checks whether the value passed in is allowed. It should raise ``GrokImportError`` if the value cannot be validated, together with a description of why not. First we define our own validation function. A validation function takes two arguments: * the name of the directive we're validating for * the value we need to validate The name can be used to format the exception properly. We'll define a validation method that only expects integer numbers:: >>> from martian.error import GrokImportError >>> class number(Directive): ... scope = CLASS ... store = ONCE ... def validate(self, value): ... if type(value) is not int: ... raise GrokImportError( ... "The '%s' directive can only be called with an integer." % ... self.name) >>> class Foo(object): ... number(3) >>> class Foo(object): ... number("This shouldn't work") Traceback (most recent call last): ... GrokImportError: The 'number' directive can only be called with an integer. Some built-in validation functions ---------------------------------- Let's look at some built-in validation functions. The ``validateText`` function determines whether a string is unicode or plain ascii:: >>> from martian import validateText >>> class title(Directive): ... scope = CLASS ... store = ONCE ... default = u'' ... validate = validateText When we pass ascii text into the directive, there is no error:: >>> class Foo(object): ... title('Some ascii text') We can also pass in a unicode string without error:: >>> class Foo(object): ... title(u'Some unicode text') Let's now try it with something that's not text at all, such as a number. This fails:: >>> class Foo(object): ... title(123) Traceback (most recent call last): ... GrokImportError: The 'title' directive can only be called with unicode or ASCII. It's not allowed to call the direct with a non-ascii encoded string:: >>> class Foo(object): ... title(u'è'.encode('latin-1')) Traceback (most recent call last): ... GrokImportError: The 'title' directive can only be called with unicode or ASCII. >>> class Foo(object): ... title(u'è'.encode('UTF-8')) Traceback (most recent call last): ... GrokImportError: The 'title' directive can only be called with unicode or ASCII. The ``validateInterfaceOrClass`` function only accepts class or interface objects:: >>> from martian import validateInterfaceOrClass >>> class klass(Directive): ... scope = CLASS ... store = ONCE ... validate = validateInterfaceOrClass It works with interfaces and classes:: >>> class Bar(object): ... pass >>> class Foo(object): ... klass(Bar) >>> from zope.interface import Interface >>> class IBar(Interface): ... pass >>> class Foo(object): ... klass(IBar) It won't work with other things:: >>> class Foo(object): ... klass(Bar()) Traceback (most recent call last): ... GrokImportError: The 'klass' directive can only be called with a class or an interface. >>> class Foo(object): ... klass(1) Traceback (most recent call last): ... GrokImportError: The 'klass' directive can only be called with a class or an interface. The ``validateInterface`` validator only accepts an interface:: >>> from martian import validateInterface >>> class iface(Directive): ... scope = CLASS ... store = ONCE ... validate = validateInterface Let's try it:: >>> class Foo(object): ... iface(IBar) It won't work with classes or other things:: >>> class Foo(object): ... iface(Bar) Traceback (most recent call last): ... GrokImportError: The 'iface' directive can only be called with an interface. >>> class Foo(object): ... iface(1) Traceback (most recent call last): ... GrokImportError: The 'iface' directive can only be called with an interface. The ``validateClass`` validator only accepts a class:: >>> from martian import validateClass >>> class klass(Directive): ... scope = CLASS ... store = ONCE ... validate = validateClass >>> class Foo(object): ... klass(Bar) But it won't work with an interface or other things:: >>> class Foo(object): ... klass(IBar) Traceback (most recent call last): ... GrokImportError: The 'klass' directive can only be called with a class. >>> class Foo(object): ... klass(Bar()) Traceback (most recent call last): ... GrokImportError: The 'klass' directive can only be called with a class. Declaring base classes ---------------------- There's a special directive called ``martian.baseclass`` which lets you declare that a certain class is the base class for a series of other components. This property should not be inherited by those components. Consider the following base class: >>> import martian >>> class MyBase(object): ... martian.baseclass() As you would expect, the directive will correctly identify this class as a baseclass: >>> martian.baseclass.bind().get(MyBase) True But, if we create a subclass of this base class, the subclass won't inherit that property, unlike with a regular directive: >>> class SubClass(MyBase): ... pass ... >>> martian.baseclass.bind().get(SubClass) False Naturally, the directive will also report a false answer if the class doesn't inherit from a base class at all and hasn't been marked with the directive: >>> class NoBase(object): ... pass ... >>> martian.baseclass.bind().get(NoBase) False Base classes influence computed directives: a directive computation will not happen on a base class (or in base classes of it). Let's define a directive with the computed rule that it will look for an object ``Context`` in the module it finds itself in and return its ``value`` attribute:: >>> class info(Directive): ... scope = CLASS ... store = ONCE ... @classmethod ... def get_default(cls, component, module, **data): ... context = getattr(module, 'Context', None) ... if context is None: ... return martian.UNKNOWN ... return context.value Let use this rule with an example where no baseclass is declared first:: >>> class basemodule(FakeModule): ... class Context(object): ... value = 1 ... class A(object): ... pass >>> from martiantest.fake import basemodule >>> class submodule(FakeModule): ... class B(basemodule.A): ... pass >>> from martiantest.fake import submodule >>> info.bind().get(submodule.B) 1 Now let's apply the rule where ``A`` is declared to be a baseclass. Since ``A`` is a base class, the computed default will not take effect:: >>> class basemodule2(FakeModule): ... class Context(object): ... value = 1 ... class A(object): ... martian.baseclass() >>> from martiantest.fake import basemodule2 >>> class submodule2(FakeModule): ... class B(basemodule2.A): ... pass >>> from martiantest.fake import submodule2 >>> info.bind().get(submodule2.B) is martian.UNKNOWN True If we change the default rule so we use ``UnknownError`` we see the same behavior, except an error message is raised:: >>> def get_default(component, module, **data): ... context = getattr(module, 'Context', None) ... if context is None: ... raise UnknownError("No Context object found!", component) ... return context.value It will work for the case where no baseclass is defined, as the rule can take effect then:: >>> info.bind(get_default=get_default).get(submodule.B) 1 But we will get a ``GrokError`` when a baseclass is in play:: >>> info.bind(get_default=get_default).get(submodule2.B) Traceback (most recent call last): ... GrokError: No Context object found! martian-0.14/src/martian/core.txt0000644000175000017500000010676611464246706014660 0ustar jwjwMartian core ============ A simple framework ------------------ Let's look at a very simple framework where files are handled by their extensions:: >>> class filehandler(FakeModule): ... import os ... ... def handle_txt(filepath): ... return "Text file" ... ... def handle_xml(filepath): ... return "XML file" ... ... extension_handlers = { '.txt': handle_txt, '.xml': handle_xml } ... ... def handle(filepath): ... name, ext = os.path.splitext(filepath) ... return extension_handlers[ext](filepath) >>> from martiantest.fake import filehandler Now let's try the ``handle`` function for a few file types:: >>> filehandler.handle('test.txt') 'Text file' >>> filehandler.handle('test2.xml') 'XML file' File extensions that we do not recognize cause a ``KeyError`` to be raised:: >>> filehandler.handle('image.png') Traceback (most recent call last): ... KeyError: '.png' We now want to plug into this filehandler framework and provide a handler for ``.png`` files:: >>> class pnghandler(FakeModule): ... def handle_png(filepath): ... return "PNG file" ... ... filehandler.extension_handlers['.png'] = handle_png >>> from martiantest.fake import pnghandler In the extension module, we manipulate the ``extension_handlers`` dictionary of the ``filehandler`` module and plug in our own function. PNG handling works now:: >>> filehandler.handle('image.png') 'PNG file' Grokkers that grok ------------------ Let's write a grokker that can grok the file types. A ``Grokker`` is an object that can *grok* objects - execute configuration actions pertaining to the grokked object, such as registering it with some central registry. Different kinds of grokkers can grok different types of objects (instances, classes, functions). Let's define a Grokker to help us register the file type handler functions as seen in our previous example:: >>> import types >>> from zope.interface import implements >>> import martian >>> class FileTypeGrokker(martian.InstanceGrokker): ... martian.component(types.FunctionType) ... ... def grok(self, name, obj, **kw): ... if not name.startswith('handle_'): ... return False ... ext = name.split('_')[1] ... filehandler.extension_handlers['.' + ext] = obj ... return True This ``InstanceGrokker`` allows us to grok instances of a particular type (such as functions). We need to define the type of object we're looking for with the ``martian.component`` directive. In the ``grok`` method, we first make sure we only grok functions that have a name that starts with ``handle_``. Then we determine the used extension from the name and register the funcion in the ``extension_handlers`` dictionary of the ``filehandler`` module. We return ``True`` if we indeed grokked the object. An instance will provide the IGrokker interface:: >>> filetype_grokker = FileTypeGrokker() >>> from martian.interfaces import IGrokker >>> IGrokker.providedBy(filetype_grokker) True Now let's use the grokker to grok a new handle function:: >>> def handle_jpg(filepath): ... return "JPG file" >>> filetype_grokker.grok('handle_jpg', handle_jpg) True After we grokked, we have registered a handler for ``.jpg`` files (the extension to register under was deduced from the function name):: >>> sorted(filehandler.extension_handlers.keys()) ['.jpg', '.png', '.txt', '.xml'] This means now our ``filehandler.handle`` function is now able to handle JPG files as well:: >>> filehandler.handle('image2.jpg') 'JPG file' If we try to grok a function that doesn't start with ``handle_`` in its name, nothing will happen:: >>> def something(filepath): ... return 'Something' >>> filetype_grokker.grok('something', something) False >>> 'something' in filehandler.extension_handlers False Grokking a module ----------------- Grokking individual components is useful, but to make Martian really useful we need to be able to grok whole modules or packages as well. Let's look at a special grokker that can grok a Python module, the ``ModuleGrokker``. The idea is that the ``ModuleGrokker`` groks any components in a module that it recognizes. A ``ModuleGrokker`` does not work alone. It needs to be supplied with one or more grokkers that can grok the components to be founded in a module:: >>> module_grokker = martian.ModuleGrokker() >>> module_grokker.register(filetype_grokker) We now define a module that defines a few filetype handlers to be grokked:: >>> class lotsofhandlers(FakeModule): ... def handle_exe(filepath): ... return "EXE file" ... ... def handle_ogg(filepath): ... return "OGG file" ... ... def handle_svg(filepath): ... return "SVG file" >>> from martiantest.fake import lotsofhandlers Let's grok it:: >>> module_grokker.grok('lotsofhandlers', lotsofhandlers) True The new registrations are now available:: >>> sorted(filehandler.extension_handlers.keys()) ['.exe', '.jpg', '.ogg', '.png', '.svg', '.txt', '.xml'] The system indeed recognizes them now:: >>> filehandler.handle('test.ogg') 'OGG file' >>> filehandler.handle('test.svg') 'SVG file' >>> filehandler.handle('test.exe') 'EXE file' As you can see, with Martian we can now define handlers without ever having to register them manually. This allows us to rewrite our original module and take out the manual registrations completely:: >>> class filehandler(FakeModule): ... import os ... ... def handle_txt(filepath): ... return "Text file" ... ... def handle_xml(filepath): ... return "XML file" ... ... extension_handlers = {} ... ... def handle(filepath): ... name, ext = os.path.splitext(filepath) ... return extension_handlers[ext](filepath) >>> from martiantest.fake import filehandler Let's use martian to do the registrations for us:: >>> module_grokker.grok('filehandler', filehandler) True >>> filehandler.handle('test.txt') 'Text file' InstanceGrokker --------------- We have seen how to grok module-level functions. Let's now grok some other kind of instance, a ``Color``:: >>> class color(FakeModule): ... class Color(object): ... def __init__(self, r, g, b): ... self.r = r ... self.g = g ... self.b = b ... def __repr__(self): ... return '' % (self.r, self.g, self.b) ... all_colors = {} >>> from martiantest.fake import color We now want a grokker that can recognize colors and put them in the ``all_colors`` dictionary, with the names as the keys, and the color object as the values. We can use ``InstanceGrokker`` to construct it:: >>> class ColorGrokker(martian.InstanceGrokker): ... martian.component(color.Color) ... def grok(self, name, obj, **kw): ... color.all_colors[name] = obj ... return True Let's create ``color_grokker`` and grok a color:: >>> color_grokker = ColorGrokker() >>> black = color.Color(0, 0, 0) # we DO consider black as a color :) >>> color_grokker.grok('black', black) True It ends up in the ``all_colors`` dictionary:: >>> color.all_colors {'black': } If we put ``color_grokker`` into a ``ModuleGrokker``, we can now grok multiple colors in a module:: >>> Color = color.Color >>> class colors(FakeModule): ... red = Color(255, 0, 0) ... green = Color(0, 255, 0) ... blue = Color(0, 0, 255) ... white = Color(255, 255, 255) >>> from martiantest.fake import colors >>> colors_grokker = martian.ModuleGrokker() >>> colors_grokker.register(color_grokker) >>> colors_grokker.grok('colors', colors) True >>> sorted(color.all_colors.items()) [('black', ), ('blue', ), ('green', ), ('red', ), ('white', )] Subclasses of ``Color`` are also grokked:: >>> class subcolors(FakeModule): ... class SpecialColor(Color): ... pass ... octarine = SpecialColor(-255, 0, -255) >>> from martiantest.fake import subcolors >>> colors_grokker.grok('subcolors', subcolors) True >>> 'octarine' in color.all_colors True MultiInstanceGrokker -------------------- In the previous section we have created a particular grokker that looks for instances of a component class, in this case ``Color``. Let's introduce another ``InstanceGrokker`` that looks for instances of ``Sound``:: >>> class sound(FakeModule): ... class Sound(object): ... def __init__(self, desc): ... self.desc = desc ... def __repr__(self): ... return '' % (self.desc) ... all_sounds = {} >>> from martiantest.fake import sound >>> class SoundGrokker(martian.InstanceGrokker): ... martian.component(sound.Sound) ... def grok(self, name, obj, **kw): ... sound.all_sounds[name] = obj ... return True >>> sound_grokker = SoundGrokker() What if we now want to look for ``Sound`` and ``Color`` instances at the same time? We have to use the ``color_grokker`` and ``sound_grokker`` at the same time, and we can do this with a ``MultiInstanceGrokker``:: >>> from martian.core import MultiInstanceGrokker >>> multi_grokker = MultiInstanceGrokker() >>> multi_grokker.register(color_grokker) >>> multi_grokker.register(sound_grokker) Let's grok a new color with our ``multi_grokker``:: >>> grey = Color(100, 100, 100) >>> multi_grokker.grok('grey', grey) True >>> 'grey' in color.all_colors True Let's grok a sound with our ``multi_grokker``:: >>> moo = sound.Sound('Moo!') >>> multi_grokker.grok('moo', moo) True >>> 'moo' in sound.all_sounds True We can also grok other objects, but this will have no effect:: >>> something_else = object() >>> multi_grokker.grok('something_else', something_else) False Let's put our ``multi_grokker`` in a ``ModuleGrokker``. We can do this by passing it explicitly to the ``ModuleGrokker`` factory:: >>> module_grokker = martian.ModuleGrokker(grokker=multi_grokker) We can now grok a module for both ``Color`` and ``Sound`` instances:: >>> Sound = sound.Sound >>> class lightandsound(FakeModule): ... dark_red = Color(150, 0, 0) ... scream = Sound('scream') ... dark_green = Color(0, 150, 0) ... cheer = Sound('cheer') >>> from martiantest.fake import lightandsound >>> module_grokker.grok('lightandsound', lightandsound) True >>> 'dark_red' in color.all_colors True >>> 'dark_green' in color.all_colors True >>> 'scream' in sound.all_sounds True >>> 'cheer' in sound.all_sounds True ClassGrokker ------------ Besides instances we can also grok classes. Let's define an application where we register classes representing animals. Animals can be given names using the ``name`` directive:: >>> from martian.directive import Directive, CLASS, ONCE >>> class animal(FakeModule): ... class name(Directive): ... scope = CLASS ... store = ONCE ... ... class Animal(object): ... def __repr__(self): ... return '' % animal.name.bind().get(self) ... ... all_animals = {} ... def create_animal(name): ... return all_animals[name]() >>> from martiantest.fake import animal Let's define a grokker that can grok an ``Animal``. We could either implement the ``grok`` method as with ``InstanceGrokkers``, or we can rely on the implementation that the baseclass already provides. In the latter case, we just have to declare what directives the grokker may want to use on the class and the implement the ``execute`` method:: >>> class AnimalGrokker(martian.ClassGrokker): ... martian.component(animal.Animal) ... martian.directive(animal.name) ... def execute(self, class_, name, **kw): ... animal.all_animals[name] = class_ ... return True Let's test our grokker:: >>> class Snake(animal.Animal): ... animal.name('snake') ... >>> animal_grokker = AnimalGrokker() >>> animal_grokker.grok('Snake', Snake) True >>> animal.all_animals.keys() ['snake'] We can create a snake now:: >>> animal.create_animal('snake') Note that we can supply a different default value for the directive default:: >>> class AnimalGrokker(AnimalGrokker): ... martian.directive(animal.name, default='generic animal') ... >>> class Generic(animal.Animal): ... pass ... >>> animal_grokker = AnimalGrokker() >>> animal_grokker.grok('Generic', Generic) True >>> sorted(animal.all_animals.keys()) ['generic animal', 'snake'] Moreover, we can also supply a default factory that may want to determine a dynamic default value based on the class that's being grokked. For instance, let's say the default name of an animal should the class name converted to lowercase letters:: >>> def default_animal_name(class_, module, **data): ... return class_.__name__.lower() ... >>> class AnimalGrokker(AnimalGrokker): ... martian.directive(animal.name, get_default=default_animal_name) ... >>> class Mouse(animal.Animal): ... pass ... >>> animal_grokker = AnimalGrokker() >>> animal_grokker.grok('Mouse', Mouse) True >>> sorted(animal.all_animals.keys()) ['generic animal', 'mouse', 'snake'] Note that these default value factories will also get the data from all directives that are in front of them in the grokker's directive list. For instance, consider the following directive: >>> class zoologicalname(animal.name): ... pass ... with the following default rule that takes the regular name as the default zoological name:: >>> def default_zoological_name(class_, module, name, **data): ... return name ... >>> class ZooAnimalGrokker(martian.ClassGrokker): ... martian.component(animal.Animal) ... martian.directive(animal.name, get_default=default_animal_name) ... martian.directive(zoologicalname, get_default=default_zoological_name) ... ... def execute(self, class_, name, zoologicalname, **kw): ... print zoologicalname ... return True ... >>> class Hippopotamus(animal.Animal): ... pass ... # No need to use animal.name(), we'll take the class name as default. ... # The zoological name is the same as well. ... >>> zoo_animal_grokker = ZooAnimalGrokker() >>> zoo_animal_grokker.grok('Hippopotamus', Hippopotamus) hippopotamus True If you pass a non-directive to ``martian.directive``, you get an error:: >>> class Test(martian.ClassGrokker): ... martian.directive('foo') Traceback (most recent call last): GrokImportError: The 'directive' directive can only be called with a directive. MethodGrokker ------------- A special kind of class grokker is the ``MethodGrokker``. It inspects the class at hand and calls ``execute`` for each *method* the class provides. Consider the following baseclass for circus animals: >>> class CircusAnimal(animal.Animal): ... def begin_show(self): ... pass ... def end_show(self): ... pass Circus animals define lots of methods which we'll collect using this grokker: >>> circus_animals = {} >>> from martian import MethodGrokker >>> class CircusAnimalGrokker(MethodGrokker): ... martian.component(CircusAnimal) ... def execute(self, class_, method, **kw): ... circus_animals.setdefault(class_.__name__, []).append(method.__name__) ... return True ... Now consider the following circus animals: >>> class Monkey(CircusAnimal): ... def climb(self): ... pass ... def _take_dump(self): ... pass ... >>> class Camel(CircusAnimal): ... def walk(self): ... pass ... def spit(self): ... pass >>> circus_animal_grokker = CircusAnimalGrokker() >>> circus_animal_grokker.grok('Monkey', Monkey) True >>> circus_animal_grokker.grok('Camel', Camel) True Let's look at the results: >>> for circus_animal, methods in sorted(circus_animals.items()): ... print "%s can %s." % (circus_animal, " and ".join(sorted(methods))) ... Camel can spit and walk. Monkey can climb. As we see, private methods (those beginning with underscores) have been ignored. Furthermore, methods inherited from the component baseclass (in this case ``CircusAnimal``) have also been ignored. If we wrote a class without any methods, we would encounter an error: >>> class Snail(CircusAnimal): ... pass >>> circus_animal_grokker.grok('Snail', Snail) Traceback (most recent call last): ... GrokError: does not define any public methods. Please add methods to this class to enable its registration. MultiClassGrokker ----------------- We now want to be able to grok the following module and have the ``Animal`` subclasses (but not the ``Chair`` class, which is not an animal) automatically become available:: >>> class animals(FakeModule): ... class Elephant(animal.Animal): ... animal.name('elephant') ... class Tiger(animal.Animal): ... animal.name('tiger') ... class Lion(animal.Animal): ... animal.name('lion') ... class Chair(object): ... animal.name('chair') >>> from martiantest.fake import animals First we need to wrap our ``AnimalGrokker`` into a ``MultiClassGrokker``:: >>> from martian.core import MultiClassGrokker >>> multi_grokker = MultiClassGrokker() >>> multi_grokker.register(animal_grokker) Now let's wrap it into a ``ModuleGrokker`` and grok the module:: >>> grokker = martian.ModuleGrokker(grokker=multi_grokker) >>> grokker.grok('animals', animals) True The animals (but not anything else) should have become available:: >>> sorted(animal.all_animals.keys()) ['elephant', 'generic animal', 'lion', 'mouse', 'snake', 'tiger'] We can create animals using their name now:: >>> animal.create_animal('elephant') >>> animal.create_animal('tiger') MultiGrokker ------------ ``MultiInstanceGrokker`` and ``MultiClassGrokker`` can grok instances and classes respectively, but a ``MultiInstanceGrokker`` won't work correctly if it runs into a class and vice versa. For that we use a ``MultiGrokker``, which can deal with the full range of objects that can be grokked, and skips those it doesn't recognize. Let's fill a ``MultiGrokker`` with a bunch of grokkers:: >>> from martian import MultiGrokker >>> multi = MultiGrokker() >>> multi.register(filetype_grokker) >>> multi.register(color_grokker) >>> multi.register(sound_grokker) >>> multi.register(animal_grokker) Let's try it with some individual objects:: >>> class Whale(animal.Animal): ... animal.name('whale') >>> multi.grok('Whale', Whale) True >>> 'whale' in animal.all_animals True This should have no effect, but not fail:: >>> my_whale = Whale() >>> multi.grok('my_whale', my_whale) False Grokked by the ColorGrokker:: >>> multi.grok('dark_grey', Color(50, 50, 50)) True >>> 'dark_grey' in color.all_colors True Grokked by the SoundGrokker:: >>> multi.grok('music', Sound('music')) True >>> 'music' in sound.all_sounds True Not grokked:: >>> class RockMusic(Sound): ... pass >>> multi.grok('RockMusic', RockMusic) False Grokked by SoundGrokker:: >>> multi.grok('rocknroll', RockMusic('rock n roll')) True >>> 'rocknroll' in sound.all_sounds True Not grokked:: >>> class Chair(object): ... pass >>> multi.grok('Chair', Chair) False Grokked by ``filetype_grokker``:: >>> def handle_py(filepath): ... return "Python file" >>> multi.grok('handle_py', handle_py) True >>> '.py' in filehandler.extension_handlers True Not grokked: >>> def foo(): ... pass >>> multi.grok('foo', foo) False Not grokked either:: >>> another = object() >>> multi.grok('another', another) False Let's make a module which has a mixture between classes and instances, some of which can be grokked:: >>> class mix(FakeModule): ... # grokked by AnimalGrokker ... class Whale(animal.Animal): ... animal.name('whale') ... # not grokked ... my_whale = Whale() ... # grokked by ColorGrokker ... dark_grey = Color(50, 50, 50) ... # grokked by SoundGrokker ... music = Sound('music') ... # not grokked ... class RockMusic(Sound): ... pass ... # grokked by SoundGrokker ... rocknroll = RockMusic('rock n roll') ... # grokked by AnimalGrokker ... class Dragon(animal.Animal): ... animal.name('dragon') ... # not grokked ... class Chair(object): ... pass ... # grokked by filetype_grokker ... def handle_py(filepath): ... return "Python file" ... # not grokked ... def foo(): ... pass ... # grokked by AnimalGrokker ... class SpermWhale(Whale): ... animal.name('sperm whale') ... # not grokked ... another = object() >>> from martiantest.fake import mix Let's construct a ``ModuleGrokker`` that can grok this module:: >>> mix_grokker = martian.ModuleGrokker(grokker=multi) Note that this is actually equivalent to calling ``ModuleGrokker`` without arguments and then calling ``register`` for the individual ``ClassGrokker`` and ``InstanceGrokker`` objects. Before we do the grokking, let's clean up our registration dictionaries:: >>> filehandler.extension_handlers = {} >>> color.all_colors = {} >>> sound.all_sounds = {} >>> animal.all_animals = {} Now we grok:: >>> mix_grokker.grok('mix', mix) True >>> sorted(filehandler.extension_handlers.keys()) ['.py'] >>> sorted(color.all_colors.keys()) ['dark_grey'] >>> sorted(sound.all_sounds.keys()) ['music', 'rocknroll'] >>> sorted(animal.all_animals.keys()) ['dragon', 'sperm whale', 'whale'] GlobalGrokker ------------- Sometimes you want to let a grok action happen for each module. The grok action could for instance read the globals of a module, or even static files associated with the module by name. Let's create a module with some global value:: >>> class g(FakeModule): ... amount = 50 >>> from martiantest.fake import g Now let's create a ``GlobalGrokker`` that reads ``amount`` and stores it in the ``read_amount`` dictionary:: >>> read_amount = {} >>> from martian import GlobalGrokker >>> class AmountGrokker(GlobalGrokker): ... def grok(self, name, module, **kw): ... read_amount[None] = module.amount ... return True Let's construct a ``ModuleGrokker`` with this ``GlobalGrokker`` registered:: >>> grokker = martian.ModuleGrokker() >>> grokker.register(AmountGrokker()) Now we grok and should pick up the right value:: >>> grokker.grok('g', g) True >>> read_amount[None] 50 Old-style class support ----------------------- So far we have only grokked either new-style classes or instances of new-style classes. It is also possible to grok old-style classes and their instances:: >>> class oldstyle(FakeModule): ... class Machine: ... pass ... all_machines = {} ... all_machine_instances = {} >>> from martiantest.fake import oldstyle Let's make a grokker for the old style class:: >>> class MachineGrokker(martian.ClassGrokker): ... martian.component(oldstyle.Machine) ... def grok(self, name, obj, **kw): ... oldstyle.all_machines[name] = obj ... return True And another grokker for old style instances:: >>> class MachineInstanceGrokker(martian.InstanceGrokker): ... martian.component(oldstyle.Machine) ... def grok(self, name, obj, **kw): ... oldstyle.all_machine_instances[name] = obj ... return True The multi grokker should succesfully grok the old-style ``Machine`` class and instances of it:: >>> multi = MultiGrokker() >>> multi.register(MachineGrokker()) >>> multi.register(MachineInstanceGrokker()) >>> class Robot(oldstyle.Machine): ... pass >>> multi.grok('Robot', Robot) True >>> oldstyle.all_machines.keys() ['Robot'] >>> robot = Robot() >>> multi.grok('robot', robot) True >>> oldstyle.all_machine_instances.keys() ['robot'] Grokking a package ------------------ A package consists of several sub modules. When grokking a package, all the files in the package will be grokked. Let's first create a simple grokker for the ``Animal`` class defined by the package:: >>> from martian.tests.testpackage import animal >>> all_animals = {} >>> class AnimalGrokker(martian.ClassGrokker): ... martian.component(animal.Animal) ... def grok(self, name, obj, **kw): ... all_animals[name] = obj ... return True The grokker will collect animals into the ``all_animals`` dictionary. Let's register this grokker for a ModuleGrokker:: >>> module_grokker = martian.ModuleGrokker() >>> module_grokker.register(AnimalGrokker()) Now let's grok the whole ``testpackage`` for animals:: >>> from martian import grok_dotted_name >>> grok_dotted_name('martian.tests.testpackage', grokker=module_grokker) We should now get some animals:: >>> sorted(all_animals.keys()) ['Animal', 'Bear', 'Dragon', 'Lizard', 'Python', 'SpermWhale', 'Whale'] Preparation and finalization ---------------------------- Before grokking a module, it may be that we need to do some preparation. This preparation can include setting up some parameters to pass along to the grokking process, for instance. We can pass a ``prepare`` function a the ModuleGrokker:: >>> class Number(object): ... def __init__(self, nr): ... self.nr = nr >>> all_numbers = {} >>> class NumberGrokker(martian.InstanceGrokker): ... martian.component(Number) ... def grok(self, name, obj, multiplier, **kw): ... all_numbers[obj.nr] = obj.nr * multiplier ... return True >>> def prepare(name, module, kw): ... kw['multiplier'] = 3 >>> module_grokker = martian.ModuleGrokker(prepare=prepare) >>> module_grokker.register(NumberGrokker()) We have created a ``prepare`` function that does one thing: create a ``multiplier`` parameter that is passed along the grokking process. The ``NumberGrokker`` makes use of this to prepare the ``all_numbers`` dictionary values. Let's try this with a module:: >>> class numbers(FakeModule): ... one = Number(1) ... two = Number(2) ... four = Number(4) >>> from martiantest.fake import numbers >>> module_grokker.grok('numbers', numbers) True >>> sorted(all_numbers.items()) [(1, 3), (2, 6), (4, 12)] You can also optionally register a finalization function, which will be run at the end of a module grok:: >>> def finalize(name, module, kw): ... all_numbers['finalized'] = True >>> module_grokker = martian.ModuleGrokker(prepare=prepare, finalize=finalize) >>> module_grokker.register(NumberGrokker()) >>> all_numbers = {} >>> module_grokker.grok('numbers', numbers) True >>> 'finalized' in all_numbers True Sanity checking --------------- Grokkers must return ``True`` if grokking succeeded, or ``False`` if it didn't. If they return something else (typically ``None`` as the programmer forgot to), the system will raise an error:: >>> class BrokenGrokker(martian.InstanceGrokker): ... martian.component(Number) ... def grok(self, name, obj, **kw): ... pass >>> module_grokker = martian.ModuleGrokker() >>> module_grokker.register(BrokenGrokker()) >>> module_grokker.grok('numbers', numbers) Traceback (most recent call last): ... GrokError: returns None instead of True or False. Let's also try this with a GlobalGrokker:: >>> class MyGrokker(GlobalGrokker): ... def grok(self, name, module, **kw): ... return "Foo" >>> module_grokker = martian.ModuleGrokker() >>> module_grokker.register(MyGrokker()) >>> module_grokker.grok('numbers', numbers) Traceback (most recent call last): ... GrokError: returns 'Foo' instead of True or False. Meta Grokkers ------------- Meta grokkers are grokkers that grok grokkers. This mechanism can be used to extend Martian. Let's register a ``ClassMetaGrokker`` that looks for subclasses of ``ClassGrokker``:: >>> from martian.core import MetaGrokker >>> class ClassMetaGrokker(MetaGrokker): ... martian.component(martian.ClassGrokker) >>> multi_grokker = MultiGrokker() >>> multi_grokker.register(ClassMetaGrokker(multi_grokker)) ``multi_grokker`` should now grok subclasses of ``ClassGrokker``, such as ``AnimalGrokker``:: >>> all_animals = {} # clean out animal registry >>> multi_grokker.grok('AnimalGrokker', AnimalGrokker) True Our multi_grokker should now also be able to grok animals:: >>> class Woodpecker(animal.Animal): ... pass >>> multi_grokker.grok('Woodpecker', Woodpecker) True A ``MetaMultiGrokker`` is a ``MultiGrokker`` that comes preconfigured with grokkers for ``ClassGrokker``, ``InstanceGrokker`` and ``GlobalGrokker``:: >>> from martian import MetaMultiGrokker >>> multi_grokker = MetaMultiGrokker() It works for ``ClassGrokker``:: >>> all_animals = {} >>> multi_grokker.grok('AnimalGrokker', AnimalGrokker) True >>> multi_grokker.grok('Woodpecker', Woodpecker) True >>> all_animals {'Woodpecker': } and for ``InstanceGrokker``:: >>> color.all_colors = {} >>> multi_grokker.grok('ColorGrokker', ColorGrokker) True >>> multi_grokker.grok('color', Color(255, 0, 0)) True >>> color.all_colors {'color': } and for ``GlobalGrokker``:: >>> read_amount = {} >>> multi_grokker.grok('AmountGrokker', AmountGrokker) True >>> grokker.grok('g', g) True >>> read_amount[None] 50 We can clear the meta multi grokker:: >>> multi_grokker.clear() It won't grok particular classes or instances anymore:: >>> multi_grokker.grok('Woodpecker', Woodpecker) False >>> multi_grokker.grok('color', Color(255, 0, 0)) False It can still grok grokkers:: >>> multi_grokker.grok('ColorGrokker', ColorGrokker) True Executing meta grokkers only once --------------------------------- In case of ``ClassGrokker`` and all other grokkers that are grokked by meta grokkers, we only want the grokking to occur once even if the same module (or package) is grokked twice:: >>> class TestOnce(object): ... pass >>> executed = [] >>> class somemodule(FakeModule): ... class TestGrokker(martian.ClassGrokker): ... martian.component(TestOnce) ... def grok(self, name, obj, **kw): ... executed.append(name) ... return True >>> from martiantest.fake import somemodule >>> module_grokker = martian.ModuleGrokker(MetaMultiGrokker()) Let's grok the module once:: >>> module_grokker.grok('somemodule', somemodule) True Let's grok it twice:: >>> module_grokker.grok('somemodule', somemodule) True Even though we have grokked it twice, it is still only registered once. We can show this by actually having it grok a ``TestOnce`` subclass:: >>> class anothermodule(FakeModule): ... class TestSub(TestOnce): ... pass >>> from martiantest.fake import anothermodule >>> module_grokker.grok('anothermodule', anothermodule) True >>> executed ['TestSub'] This also works for instance grokkers:: >>> class TestInstanceOnce(object): ... pass >>> executed = [] >>> class somemodule(FakeModule): ... class TestGrokker(martian.InstanceGrokker): ... martian.component(TestInstanceOnce) ... def grok(self, name, obj, **kw): ... executed.append(name) ... return True >>> from martiantest.fake import somemodule >>> module_grokker.clear() >>> module_grokker.grok('somemodule', somemodule) # once True >>> module_grokker.grok('somemodule', somemodule) # twice True >>> class anothermodule(FakeModule): ... test = TestInstanceOnce() >>> from martiantest.fake import anothermodule >>> module_grokker.grok('anothermodule', anothermodule) True >>> executed ['test'] It also works for global grokkers:: >>> executed = [] >>> class somemodule(FakeModule): ... class TestGrokker(GlobalGrokker): ... def grok(self, name, obj, **kw): ... executed.append(name) ... return True >>> from martiantest.fake import somemodule >>> module_grokker.clear() >>> module_grokker.grok('somemodule', somemodule) # once True >>> module_grokker.grok('somemodule', somemodule) # twice True The second grokking will already make ``somemodule`` grokked:: >>> executed ['somemodule'] Now let's grok another module:: >>> class anothermodule(FakeModule): ... pass >>> from martiantest.fake import anothermodule >>> module_grokker.grok('anothermodule', anothermodule) True >>> executed ['somemodule', 'anothermodule'] Priority -------- When grokking a module using a ``ModuleGrokker``, grokker execution can be determined by their priority. By default, grokkers have a priority of ``0``. Let's define two base classes, ``A`` and ``B``, which can be grokked:: >>> class A(object): ... pass >>> class B(object): ... pass Let's define a special kind of class grokker that records the order in which names get grokked:: >>> order = [] >>> class OrderGrokker(martian.ClassGrokker): ... def grok(self, name, obj, **kw): ... order.append(name) ... return True Now we define two grokkers for subclasses of ``A`` and ``B``, where the ``BGrokker`` has a higher priority:: >>> class AGrokker(OrderGrokker): ... martian.component(A) >>> class BGrokker(OrderGrokker): ... martian.component(B) ... martian.priority(10) Let's register these grokkers:: >>> multi_grokker = MetaMultiGrokker() >>> multi_grokker.grok('AGrokker', AGrokker) True >>> multi_grokker.grok('BGrokker', BGrokker) True Let's create a module containing ``A`` and ``B`` subclasses:: >>> class mymodule(FakeModule): ... class ASub(A): ... pass ... class BSub(B): ... pass >>> from martiantest.fake import mymodule We'll grok it:: >>> module_grokker = martian.ModuleGrokker(multi_grokker) >>> module_grokker.grok('mymodule', mymodule) True Since the ``BGrokker`` has a higher priority, we expect the following order of grokking:: >>> order ['BSub', 'ASub'] This also works for GlobalGrokkers. We will define a GlobalGrokker that has a higher priority than the default, but lower than B:: >>> class MyGlobalGrokker(GlobalGrokker): ... martian.priority(5) ... def grok(self, name, obj, **kw): ... order.append(name) ... return True >>> multi_grokker.grok('MyGlobalGrokker', MyGlobalGrokker) True We will grok the module again:: >>> order = [] >>> module_grokker.grok('mymodule', mymodule) True This time, the global grokker should appear after 'BSub' but before 'ASub':: >>> order ['BSub', 'mymodule', 'ASub'] Module info ----------- In addition to the ``name`` and ``object`` positional arguments, grokkers will get also get a ``module_info`` keyword argument. It is an ``IModuleInfo`` object which can be used, for example, to query module annotations. Consider the following grokker: >>> from martian.error import GrokError >>> class AnnotationsGrokker(GlobalGrokker): ... def grok(self, name, module, module_info, **kw): ... ann = module_info.getAnnotation('some.annotation', None) ... if ann is None: ... raise GrokError('Did not find annotation!', module) ... if ann != 'ME GROK SAY HI': ... raise GrokError('Wrong annotation!', module) ... return True Now let's provide a fake module: >>> import new, sys >>> annotations = new.module('annotations') >>> annotations.__file__ = '/fake/module/annotations.py' >>> sys.modules['annotations'] = annotations Clearly, it can't find the module-level variable yet: >>> module_grokker = martian.ModuleGrokker() >>> module_grokker.register(AnnotationsGrokker()) >>> import martian >>> martian.grok_dotted_name('annotations', module_grokker) Traceback (most recent call last): ... GrokError: Did not find annotation! Let's provide the annotation so that the grokker works as expected: >>> annotations.__some_annotation__ = 'ME GROK SAY HI' >>> martian.grok_dotted_name('annotations', module_grokker) Finally clean up: >>> del sys.modules['annotations'] martian-0.14/src/martian/components.py0000644000175000017500000000711011464246706015705 0ustar jwjw############################################################################## # # Copyright (c) 2006-2007 Zope Foundation 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. # ############################################################################## from zope.interface import implements from martian import util from martian.error import GrokError from martian.interfaces import IGrokker, IComponentGrokker from martian.martiandirective import directive, component class GrokkerBase(object): implements(IGrokker) def grok(self, name, obj, **kw): raise NotImplementedError class GlobalGrokker(GrokkerBase): """Grokker that groks once per module. """ def grok(self, name, obj, **kw): raise NotImplementedError class ClassGrokker(GrokkerBase): """Grokker that groks classes in a module. """ implements(IComponentGrokker) def grok(self, name, class_, module_info=None, **kw): module = None if module_info is not None: module = module_info.getModule() # Populate the data dict with information from the directives: for d in directive.bind().get(self.__class__): kw[d.name] = d.get(class_, module, **kw) return self.execute(class_, **kw) def execute(self, class_, **data): raise NotImplementedError class MethodGrokker(ClassGrokker): def grok(self, name, class_, module_info=None, **kw): module = None if module_info is not None: module = module_info.getModule() # Populate the data dict with information from class or module directives = directive.bind().get(self.__class__) for d in directives: kw[d.name] = d.get(class_, module, **kw) # Ignore methods that are present on the component baseclass. basemethods = set(util.public_methods_from_class( component.bind().get(self.__class__))) methods = set(util.public_methods_from_class(class_)) - basemethods if not methods: raise GrokError("%r does not define any public methods. " "Please add methods to this class to enable " "its registration." % class_, class_) results = [] for method in methods: # Directives may also be applied to methods, so let's # check each directive and potentially override the # class-level value with a value from the method *locally*. data = kw.copy() for bound_dir in directives: d = bound_dir.directive class_value = data[bound_dir.name] data[bound_dir.name] = d.store.get(d, method, default=class_value) results.append(self.execute(class_, method, **data)) return max(results) def execute(self, class_, method, **data): raise NotImplementedError class InstanceGrokker(GrokkerBase): """Grokker that groks instances in a module. """ implements(IComponentGrokker) def grok(self, name, class_, **kw): return self.execute(class_, **kw) def execute(self, class_, **kw): raise NotImplementedError martian-0.14/src/martian/context.py0000644000175000017500000000344511464246706015213 0ustar jwjwfrom martian.directive import UnknownError from martian.util import scan_for_classes class GetDefaultComponentFactory(object): def __init__(self, iface, component_name, directive_name): """Create a get_default_component function. iface - the iface that the component to be associated implements. for example: IContext component_name - the name of the type of thing we are looking for. for example: context directive_name - the name of the directive in use. for example: grok.context. """ self.iface = iface self.component_name = component_name self.directive_name = directive_name def __call__(self, component, module, **data): """Determine module-level component. Look for components in module. iface determines the kind of module-level component to look for (it will implement iface). If there is no module-level component, raise an error. If there is one module-level component, it is returned. If there are more than one module-level component, raise an error. """ components = list(scan_for_classes(module, self.iface)) if len(components) == 0: raise UnknownError( "No module-level %s for %r, please use the '%s' " "directive." % (self.component_name, component, self.directive_name), component) elif len(components) == 1: return components[0] else: raise UnknownError( "Multiple possible %ss for %r, please use the '%s' " "directive." % (self.component_name, component, self.directive_name), component) martian-0.14/src/martian/context.txt0000644000175000017500000001352411464246706015401 0ustar jwjwContext associating directives ============================== Martian can help you implement directives that implicitly associate with another object or class in the modules. The most common example of this is the way Grok's ``context`` directive works. It has the following rules: * ``grok.context`` can be used on the class to establish the context class for this class. * ``grok.context`` can be used on the module to establish the context class for all classes in the module that require a context. Only class-level ``grok.context`` use will override this. * If there is no ``grok.context`` for the class or module, the context will be a class in the module that implements a special ``IContext`` interface. * If there are multiple possible impicit contexts, the context is ambiguous. This is an error. * If there is no possible implicit context, the context cannot be established. This is an error too. Let's implement a context directive with this behavior:: >>> import martian >>> class context(martian.Directive): ... scope = martian.CLASS_OR_MODULE ... store = martian.ONCE Let's use an explicit class context:: >>> class A(object): ... pass >>> class explicitclasscontext(FakeModule): ... class B(object): ... context(A) >>> from martiantest.fake import explicitclasscontext >>> context.bind().get(explicitclasscontext.B) Let's now use the directive on the module-level, explicitly:: >>> class explicitmodulecontext(FakeModule): ... context(A) ... class B(object): ... pass >>> from martiantest.fake import explicitmodulecontext >>> context.bind().get(explicitmodulecontext.B) XXX why does this get this put into martiantest.fake.explicitmodule? A problem in FakeModule? Let's see a combination of the two, to check whether context on the class level overrides that on the module level:: >>> class D(object): ... pass >>> class explicitcombo(FakeModule): ... context(A) ... class B(object): ... pass ... class C(object): ... context(D) >>> from martiantest.fake import explicitcombo >>> context.bind().get(explicitcombo.B) >>> context.bind().get(explicitcombo.C) So far so good. Now let's look at automatic association. Let's provide a ``get_default`` function that associates with any class that implements ``IContext``: >>> from zope.interface import Interface >>> class IContext(Interface): ... pass >>> get_default_context = martian.GetDefaultComponentFactory( ... IContext, 'context', 'context') We define a base class that will be automatically associated with:: >>> from zope.interface import implements >>> class Context(object): ... implements(IContext) Let's experiment whether implicit context association works:: >>> class implicitcontext(FakeModule): ... class A(Context): ... pass ... class B(object): ... pass >>> from martiantest.fake import implicitcontext >>> context.bind(get_default=get_default_context).get(implicitcontext.B) We now test the failure conditions. There is no implicit context to associate with:: >>> class noimplicitcontext(FakeModule): ... class B(object): ... pass >>> from martiantest.fake import noimplicitcontext >>> context.bind(get_default=get_default_context).get(noimplicitcontext.B) Traceback (most recent call last): ... GrokError: No module-level context for , please use the 'context' directive. There are too many possible contexts:: >>> class ambiguouscontext(FakeModule): ... class A(Context): ... pass ... class B(Context): ... pass ... class C(object): ... pass >>> from martiantest.fake import ambiguouscontext >>> context.bind(get_default=get_default_context).get(ambiguouscontext.B) Traceback (most recent call last): ... GrokError: Multiple possible contexts for , please use the 'context' directive. Let's try this with inheritance, where an implicit context is provided by a base class defined in another module:: >>> class basemodule(FakeModule): ... class A(Context): ... pass ... class B(object): ... pass >>> from martiantest.fake import basemodule >>> class submodule(FakeModule): ... class C(basemodule.B): ... pass >>> from martiantest.fake import submodule >>> context.bind(get_default=get_default_context).get(submodule.C) Let's try it again with an ambiguous context in this case, resolved because there is an unambiguous context for the base class ``B``:: >>> class basemodule2(FakeModule): ... class A(Context): ... pass ... class B(object): ... pass >>> from martiantest.fake import basemodule2 >>> class submodule2(FakeModule): ... class Ambiguous1(Context): ... pass ... class Ambiguous2(Context): ... pass ... class C(basemodule2.B): ... pass >>> from martiantest.fake import submodule2 >>> context.bind(get_default=get_default_context).get(submodule2.C) If the implicit context cannot be found in the base class either, the error will show up for the most specific class (``C``):: >>> class basemodule3(FakeModule): ... class B(object): ... pass >>> from martiantest.fake import basemodule3 >>> class submodule3(FakeModule): ... class C(basemodule3.B): ... pass >>> from martiantest.fake import submodule3 >>> context.bind(get_default=get_default_context).get(submodule3.C) Traceback (most recent call last): ... GrokError: No module-level context for , please use the 'context' directive. martian-0.14/src/martian/core.py0000644000175000017500000001702011464246706014451 0ustar jwjwimport types, inspect from zope.interface import implements from martian.interfaces import IGrokker, IMultiGrokker from martian import util, scan from martian.components import (GrokkerBase, ClassGrokker, InstanceGrokker, GlobalGrokker) from martian.error import GrokError from martian.martiandirective import component, priority class MultiGrokkerBase(GrokkerBase): implements(IMultiGrokker) def register(self, grokker): raise NotImplementedError def grok(self, name, obj, **kw): grokked_status = False for g, name, obj in self.grokkers(name, obj): grokked = g.grok(name, obj, **kw) if grokked not in (True, False): raise GrokError( "%r returns %r instead of True or False." % (g, grokked), None) if grokked: grokked_status = True return grokked_status def clear(self): raise NotImplementedError def grokkers(self, name, obj): raise NotImplementedError def _grokker_sort_key((grokker, name, obj)): """Helper function to calculate sort order of grokker. """ return priority.bind().get(grokker) class ModuleGrokker(MultiGrokkerBase): def __init__(self, grokker=None, prepare=None, finalize=None): if grokker is None: grokker = MultiGrokker() self._grokker = grokker self.prepare = prepare self.finalize = finalize def register(self, grokker): self._grokker.register(grokker) def clear(self): self._grokker.clear() def grok(self, name, module, **kw): grokked_status = False # prepare module grok - this can also influence the kw dictionary if self.prepare is not None: self.prepare(name, module, kw) # sort grokkers by priority grokkers = sorted(self.grokkers(name, module), key=_grokker_sort_key, reverse=True) for g, name, obj in grokkers: grokked = g.grok(name, obj, **kw) if grokked not in (True, False): raise GrokError( "%r returns %r instead of True or False." % (g, grokked), None) if grokked: grokked_status = True # finalize module grok if self.finalize is not None: self.finalize(name, module, kw) return grokked_status def grokkers(self, name, module): grokker = self._grokker # get any global grokkers for t in grokker.grokkers(name, module): yield t # try to grok everything in module for name in dir(module): if '.' in name: # This must be a module-level variable that couldn't # have been set by the developer. It must have been a # module-level directive. continue obj = getattr(module, name) if not util.defined_locally(obj, module.__name__): continue if util.is_baseclass(obj): continue for t in grokker.grokkers(name, obj): yield t class MultiInstanceOrClassGrokkerBase(MultiGrokkerBase): def __init__(self): self.clear() def register(self, grokker): key = component.bind().get(grokker) grokkers = self._grokkers.setdefault(key, []) for g in grokkers: if g.__class__ is grokker.__class__: return grokkers.append(grokker) def clear(self): self._grokkers = {} def grokkers(self, name, obj): used_grokkers = set() for base in self.get_bases(obj): grokkers = self._grokkers.get(base) if grokkers is None: continue for grokker in grokkers: if grokker not in used_grokkers: yield grokker, name, obj used_grokkers.add(grokker) class MultiInstanceGrokker(MultiInstanceOrClassGrokkerBase): def get_bases(self, obj): return inspect.getmro(obj.__class__) class MultiClassGrokker(MultiInstanceOrClassGrokkerBase): def get_bases(self, obj): if type(obj) is types.ModuleType: return [] return inspect.getmro(obj) class MultiGlobalGrokker(MultiGrokkerBase): def __init__(self): self.clear() def register(self, grokker): for g in self._grokkers: if grokker.__class__ is g.__class__: return self._grokkers.append(grokker) def clear(self): self._grokkers = [] def grokkers(self, name, module): for grokker in self._grokkers: yield grokker, name, module class MultiGrokker(MultiGrokkerBase): def __init__(self): self.clear() def register(self, grokker): if isinstance(grokker, InstanceGrokker): self._multi_instance_grokker.register(grokker) elif isinstance(grokker, ClassGrokker): self._multi_class_grokker.register(grokker) elif isinstance(grokker, GlobalGrokker): self._multi_global_grokker.register(grokker) else: assert 0, "Unknown type of grokker: %r" % grokker def clear(self): self._multi_instance_grokker = MultiInstanceGrokker() self._multi_class_grokker = MultiClassGrokker() self._multi_global_grokker = MultiGlobalGrokker() def grokkers(self, name, obj): if isinstance(obj, (type, types.ClassType)): return self._multi_class_grokker.grokkers(name, obj) elif isinstance(obj, types.ModuleType): return self._multi_global_grokker.grokkers(name, obj) else: return self._multi_instance_grokker.grokkers(name, obj) class MetaMultiGrokker(MultiGrokker): """Multi grokker which comes pre-registered with meta-grokkers. """ def clear(self): super(MetaMultiGrokker, self).clear() # bootstrap the meta-grokkers self.register(ClassMetaGrokker(self)) self.register(InstanceMetaGrokker(self)) self.register(GlobalMetaGrokker(self)) def grok_dotted_name(dotted_name, grokker, exclude_filter=None, ignore_nonsource=True, **kw): module_info = scan.module_info_from_dotted_name(dotted_name, exclude_filter, ignore_nonsource) grok_package(module_info, grokker, **kw) def grok_package(module_info, grokker, **kw): grok_module(module_info, grokker, **kw) for sub_module_info in module_info.getSubModuleInfos(): grok_package(sub_module_info, grokker, **kw) def grok_module(module_info, grokker, **kw): grokker.grok(module_info.dotted_name, module_info.getModule(), module_info=module_info, **kw) # deep meta mode here - we define grokkers that can pick up the # three kinds of grokker: ClassGrokker, InstanceGrokker and ModuleGrokker class MetaGrokker(ClassGrokker): def __init__(self, multi_grokker): """multi_grokker - the grokker to register grokkers with. """ self.multi_grokker = multi_grokker def grok(self, name, obj, **kw): self.multi_grokker.register(obj()) return True class ClassMetaGrokker(MetaGrokker): component(ClassGrokker) class InstanceMetaGrokker(MetaGrokker): component(InstanceGrokker) class GlobalMetaGrokker(MetaGrokker): component(GlobalGrokker) class GrokkerRegistry(ModuleGrokker): def __init__(self): super(GrokkerRegistry, self).__init__(MetaMultiGrokker()) martian-0.14/src/martian/tests/0000755000175000017500000000000011464246723014310 5ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/0000755000175000017500000000000011464246723020045 5ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/subpackage/0000755000175000017500000000000011464246723022152 5ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/subpackage/__init__.py0000644000175000017500000000000011464246705024251 0ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/tests/0000755000175000017500000000000011464246723021207 5ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/tests/subpackage/0000755000175000017500000000000011464246723023314 5ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/tests/subpackage/__init__.py0000644000175000017500000000000011464246705025413 0ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/tests/__init__.py0000644000175000017500000000000011464246705023306 0ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/__init__.py0000644000175000017500000000000011464246705022144 0ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/ftests/0000755000175000017500000000000011464246723021355 5ustar jwjwmartian-0.14/src/martian/tests/withtestspackages/ftests/__init__.py0000644000175000017500000000000011464246705023454 0ustar jwjwmartian-0.14/src/martian/tests/withtestsmodules/0000755000175000017500000000000011464246723017737 5ustar jwjwmartian-0.14/src/martian/tests/withtestsmodules/subpackage/0000755000175000017500000000000011464246723022044 5ustar jwjwmartian-0.14/src/martian/tests/withtestsmodules/subpackage/__init__.py0000644000175000017500000000000011464246705024143 0ustar jwjwmartian-0.14/src/martian/tests/withtestsmodules/__init__.py0000644000175000017500000000000011464246705022036 0ustar jwjwmartian-0.14/src/martian/tests/withtestsmodules/ftests.py0000644000175000017500000000013211464246705021615 0ustar jwjwimport unittest def test_suite(): return unittest.TestSuite() # return an empty suite martian-0.14/src/martian/tests/withtestsmodules/tests.py0000644000175000017500000000013211464246705021447 0ustar jwjwimport unittest def test_suite(): return unittest.TestSuite() # return an empty suite martian-0.14/src/martian/tests/public_methods_from_class.txt0000644000175000017500000000166711464246705022274 0ustar jwjwConsider the following class >>> class A(object): ... an_attribute = 42 ... ... def __init__(self): ... pass # this method is ignored ... ... def __call__(self): ... pass # this method is ignored ... ... def __double_underscored(self): ... pass # this method is ignored ... ... def _single_underscored(self): ... pass # this method is ignored ... ... def should_be_public(self): ... pass # this method is found ... ... def should_also_be_public(self): ... pass # this method is found ... With martian's ``public_methods_from_class`` helper we can extract all public methods from this class, in other words, all methods that do not begin with an underscore: >>> from martian import util >>> methods = util.public_methods_from_class(A) >>> sorted([m.__name__ for m in methods]) ['should_also_be_public', 'should_be_public'] martian-0.14/src/martian/tests/withpyconly/0000755000175000017500000000000011464246723016701 5ustar jwjwmartian-0.14/src/martian/tests/withpyconly/foo.py0000644000175000017500000000001011464246705020025 0ustar jwjw# hello martian-0.14/src/martian/tests/withpyconly/subpackage/0000755000175000017500000000000011464246723021006 5ustar jwjwmartian-0.14/src/martian/tests/withpyconly/subpackage/__init__.py0000644000175000017500000000000011464246705023105 0ustar jwjwmartian-0.14/src/martian/tests/withpyconly/__init__.py0000644000175000017500000000000011464246705021000 0ustar jwjwmartian-0.14/src/martian/tests/scanforclasses/0000755000175000017500000000000011464246723017321 5ustar jwjwmartian-0.14/src/martian/tests/scanforclasses/test4.py0000644000175000017500000000076711464246705020750 0ustar jwjw# this is a module has a single Context subclass in it. # this module is used in a scan_for_context.txt test. import os from martian.tests.scanforclasses import IContext, Context from zope.interface import implements foo = "Bar" class Qux(object): pass class Hallo: pass class MyContext(object): implements(IContext) class MyContext2(Context): pass class MyContext3(MyContext): pass class MyContext4(MyContext2): pass qux = Qux() hallo = Hallo() mycontext = MyContext() martian-0.14/src/martian/tests/scanforclasses/test3.py0000644000175000017500000000057211464246705020741 0ustar jwjw# this is a module has a single Context subclass in it. # this module is used in a scan_for_context.txt test. import os from martian.tests.scanforclasses import IContext from zope.interface import implements foo = "Bar" class Qux(object): pass class Hallo: pass class MyContext(object): implements(IContext) qux = Qux() hallo = Hallo() mycontext = MyContext() martian-0.14/src/martian/tests/scanforclasses/test2.py0000644000175000017500000000050411464246705020733 0ustar jwjw# this is a module has a single Context subclass in it. # this module is used in a scan_for_context.txt test. import os from martian.tests.scanforclasses import Context foo = "Bar" class Qux(object): pass class Hallo: pass class MyContext(Context): pass qux = Qux() hallo = Hallo() mycontext = MyContext() martian-0.14/src/martian/tests/scanforclasses/__init__.py0000644000175000017500000000020711464246705021431 0ustar jwjwfrom zope.interface import Interface, implements class IContext(Interface): pass class Context(object): implements(IContext) martian-0.14/src/martian/tests/scanforclasses/test1.py0000644000175000017500000000041011464246705020726 0ustar jwjw# this is a module that has deliberately NO Context subclass, nor a class # that implements IContext class. It is used to write a scan_for_context.txt # test. import os foo = "Bar" class Qux(object): pass class Hallo: pass qux = Qux() hallo = Hallo() martian-0.14/src/martian/tests/stoneage/0000755000175000017500000000000011464246723016115 5ustar jwjwmartian-0.14/src/martian/tests/stoneage/notpackage/0000755000175000017500000000000011464246723020231 5ustar jwjwmartian-0.14/src/martian/tests/stoneage/notpackage/dummy.py0000644000175000017500000000000611464246705021732 0ustar jwjw# foo martian-0.14/src/martian/tests/stoneage/cave.py0000644000175000017500000000011611464246705017403 0ustar jwjw class Cave(object): pass class Index(object): pass index = Index() martian-0.14/src/martian/tests/stoneage/painting/0000755000175000017500000000000011464246723017726 5ustar jwjwmartian-0.14/src/martian/tests/stoneage/painting/__init__.py0000644000175000017500000000002411464246705022033 0ustar jwjw# this is a package martian-0.14/src/martian/tests/stoneage/__init__.py0000644000175000017500000000006711464246705020231 0ustar jwjw# this package is a test fixture for the scan tests. martian-0.14/src/martian/tests/stoneage/hunt/0000755000175000017500000000000011464246723017073 5ustar jwjwmartian-0.14/src/martian/tests/stoneage/hunt/mammoth_templates/0000755000175000017500000000000011464246723022613 5ustar jwjwmartian-0.14/src/martian/tests/stoneage/hunt/mammoth_templates/index.pt0000644000175000017500000000007511464246705024271 0ustar jwjw

ME GROK HUNT MAMMOTH!

martian-0.14/src/martian/tests/stoneage/hunt/mammoth.py0000644000175000017500000000010011464246705021076 0ustar jwjw class Mammoth(object): pass class Index(object): pass martian-0.14/src/martian/tests/stoneage/hunt/__init__.py0000644000175000017500000000002411464246705021200 0ustar jwjw# this is a package martian-0.14/src/martian/tests/scan_for_classes.txt0000644000175000017500000000205311464246705020360 0ustar jwjwScanning for the context object ------------------------------- Let's import a module that contains no ``Context`` subclass, nor classes that implement ``IContext``:: >>> from martian.tests.scanforclasses import IContext We shouldn't see any classes that are contexts:: >>> from martian.util import scan_for_classes >>> from martian.tests.scanforclasses import test1 >>> list(scan_for_classes(test1, IContext)) [] Now we look at a module with a single ``Context`` subclass:: >>> from martian.tests.scanforclasses import test2 >>> list(scan_for_classes(test2, IContext)) [] Now we'll look at a module with a single class that implements ``IContext``:: >>> from martian.tests.scanforclasses import test3 >>> list(scan_for_classes(test3, IContext)) [] Let's finish by looking at a module which defines multiple contexts:: >>> from martian.tests.scanforclasses import test4 >>> len(list(scan_for_classes(test4, IContext))) 4 martian-0.14/src/martian/tests/test_all.py0000644000175000017500000000325611464246705016477 0ustar jwjwimport unittest from zope.testing import doctest from martian.testing import FakeModule optionflags = doctest.NORMALIZE_WHITESPACE + doctest.ELLIPSIS globs = dict(FakeModule=FakeModule) def test_suite(): suite = unittest.TestSuite() suite.addTests([ doctest.DocFileSuite('README.txt', package='martian', globs=globs, optionflags=optionflags), doctest.DocFileSuite('scan.txt', package='martian', optionflags=optionflags), doctest.DocFileSuite('directive.txt', package='martian', globs=globs, optionflags=optionflags), doctest.DocFileSuite('core.txt', package='martian', globs=globs, optionflags=optionflags), doctest.DocFileSuite('edgecase.txt', package='martian', globs=globs, optionflags=optionflags), doctest.DocFileSuite('scan_for_classes.txt', package='martian.tests', optionflags=optionflags), doctest.DocFileSuite('public_methods_from_class.txt', package='martian.tests', optionflags=optionflags), doctest.DocFileSuite('context.txt', package='martian', globs=globs, optionflags=optionflags), ]) return suite martian-0.14/src/martian/tests/with__main__/0000755000175000017500000000000011464246723016724 5ustar jwjwmartian-0.14/src/martian/tests/with__main__/package.py0000644000175000017500000000004111464246705020664 0ustar jwjw class Package(object): pass martian-0.14/src/martian/tests/with__main__/__init__.py0000644000175000017500000000002411464246705021031 0ustar jwjw# this is a package martian-0.14/src/martian/tests/with__main__/__main__.py0000644000175000017500000000005011464246705021011 0ustar jwjw def main(): print "Hello" main() martian-0.14/src/martian/tests/__init__.py0000644000175000017500000000000211464246705016411 0ustar jwjw# martian-0.14/src/martian/tests/testpackage/0000755000175000017500000000000011464246723016603 5ustar jwjwmartian-0.14/src/martian/tests/testpackage/two.py0000644000175000017500000000006311464246705017765 0ustar jwjwimport animal class Bear(animal.Animal): pass martian-0.14/src/martian/tests/testpackage/alpha/0000755000175000017500000000000011464246723017670 5ustar jwjwmartian-0.14/src/martian/tests/testpackage/alpha/__init__.py0000644000175000017500000000012411464246705021776 0ustar jwjwfrom martian.tests.testpackage import animal class Python(animal.Animal): pass martian-0.14/src/martian/tests/testpackage/animal.py0000644000175000017500000000003711464246705020416 0ustar jwjwclass Animal(object): pass martian-0.14/src/martian/tests/testpackage/__init__.py0000644000175000017500000000000411464246705020706 0ustar jwjw# martian-0.14/src/martian/tests/testpackage/beta/0000755000175000017500000000000011464246723017516 5ustar jwjwmartian-0.14/src/martian/tests/testpackage/beta/__init__.py0000644000175000017500000000000211464246705021617 0ustar jwjw# martian-0.14/src/martian/tests/testpackage/beta/three.py0000644000175000017500000000012511464246705021175 0ustar jwjwfrom martian.tests.testpackage import animal class Lizard(animal.Animal): pass martian-0.14/src/martian/tests/testpackage/one.py0000644000175000017500000000017611464246705017742 0ustar jwjwimport animal class Whale(animal.Animal): pass class Dragon(animal.Animal): pass class SpermWhale(Whale): pass martian-0.14/src/martian/tests/withbogusmodules/0000755000175000017500000000000011464246723017714 5ustar jwjwmartian-0.14/src/martian/tests/withbogusmodules/.bogussubpackage/0000755000175000017500000000000011464246723023137 5ustar jwjwmartian-0.14/src/martian/tests/withbogusmodules/.bogussubpackage/__init__.py0000644000175000017500000000000011464246705025236 0ustar jwjwmartian-0.14/src/martian/tests/withbogusmodules/.bogus.py0000644000175000017500000000006311464246705021462 0ustar jwjw# starts with a dot so not really a python module martian-0.14/src/martian/tests/withbogusmodules/subpackage/0000755000175000017500000000000011464246723022021 5ustar jwjwmartian-0.14/src/martian/tests/withbogusmodules/subpackage/__init__.py0000644000175000017500000000000011464246705024120 0ustar jwjwmartian-0.14/src/martian/tests/withbogusmodules/nonbogus.py0000644000175000017500000000003211464246705022113 0ustar jwjw# really a python module. martian-0.14/src/martian/tests/withbogusmodules/__init__.py0000644000175000017500000000000011464246705022013 0ustar jwjwmartian-0.14/src/martian/tests/withbogusmodules/1alsobogus.py0000644000175000017500000000001611464246705022342 0ustar jwjw# also bogus martian-0.14/src/martian/README.txt0000644000175000017500000003776311464246706014665 0ustar jwjwMartian tutorial **************** Introduction ============ "There was so much to grok, so little to grok from." -- Stranger in a Strange Land, by Robert A. Heinlein Martian provides infrastructure for declarative configuration of Python code. Martian is especially useful for the construction of frameworks that need to provide a flexible plugin infrastructure. Martian doesn't actually provide infrastructure for plugin registries (except for itself). Many frameworks have their own systems for this, and if you need a generic one, you might want to consider ``zope.component``. Martian just allows you to make the registration of plugins less verbose. You can see Martian as doing something that you can also solve with metaclasses, with the following advantages: * the developer of the framework doesn't have to write a lot of ad-hoc metaclasses anymore; instead we offer an infrastructure to make life easier. * configuration doesn't need to happen at import time, but can happen at program startup time. This also makes configuration more tractable for a developer. * we don't bother the developer that *uses* the framework with the surprising behavior that metaclasses sometimes bring. The classes the user has to deal with are normal classes. Why is this package named ``martian``? In the novel "Stranger in a Strange Land", the verb *grok* is introduced: Grok means to understand so thoroughly that the observer becomes a part of the observed -- to merge, blend, intermarry, lose identity in group experience. In the context of this package, "grokking" stands for the process of deducing declarative configuration actions from Python code. In the novel, grokking is originally a concept that comes from the planet Mars. Martians *grok*. Since this package helps you grok code, it's called Martian. Martian provides a framework that allows configuration to be expressed in declarative Python code. These declarations can often be deduced from the structure of the code itself. The idea is to make these declarations so minimal and easy to read that even extensive configuration does not overly burden the programmers working with the code. The ``martian`` package is a spin-off from the `Grok project`_, in the context of which this codebase was first developed. While Grok uses it, the code is completely independent of Grok. .. _`Grok project`: http://grok.zope.org Motivation ========== "Deducing declarative configuration actions from Python code" - that sounds very abstract. What does it actually mean? What is configuration? What is declarative configuration? In order to explain this, we'll first take a look at configuration. Larger frameworks often offer a lot of points where you can modify their behavior: ways to combine its own components with components you provide yourself to build a larger application. A framework offers points where it can be *configured* with plugin code. When you plug some code into a plugin point, it results in the updating of some registry somewhere with the new plugin. When the framework uses a plugin, it will first look it up in the registry. The action of registering some component into a registry can be called *configuration*. Let's look at an example framework that offers a plugin point. We introduce a very simple framework for plugging in different template languages, where each template language uses its own extension. You can then supply the framework with the template body and the template extension and some data, and render the template. Let's look at the framework:: >>> import string >>> class templating(FakeModule): ... ... class InterpolationTemplate(object): ... "Use %(foo)s for dictionary interpolation." ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... return self.text % kw ... ... class TemplateStringTemplate(object): ... "PEP 292 string substitutions." ... def __init__(self, text): ... self.template = string.Template(text) ... def render(self, **kw): ... return self.template.substitute(**kw) ... ... # the registry, we plug in the two templating systems right away ... extension_handlers = { '.txt': InterpolationTemplate, ... '.tmpl': TemplateStringTemplate } ... ... def render(data, extension, **kw): ... """Render the template at filepath with arguments. ... ... data - the data in the file ... extension - the extension of the file ... keyword arguments - variables to interpolate ... ... In a real framework you could pass in the file path instead of ... data and extension, but we don't want to open files in our ... example. ... ... Returns the rendered template ... """ ... template = extension_handlers[extension](data) ... return template.render(**kw) Since normally we cannot create modules in a doctest, we have emulated the ``templating`` Python module using the ``FakeModule`` class. Whenever you see ``FakeModule`` subclasses, imagine you're looking at a module definition in a ``.py`` file. Now that we have defined a module ``templating``, we also need to be able to import it. Fake modules are always placed automatically into the ``martiantest.fake`` namespace so you can import them from there:: >>> from martiantest.fake import templating Now let's try the ``render`` function for the registered template types, to demonstrate that our framework works:: >>> templating.render('Hello %(name)s!', '.txt', name="world") 'Hello world!' >>> templating.render('Hello ${name}!', '.tmpl', name="universe") 'Hello universe!' File extensions that we do not recognize cause a ``KeyError`` to be raised:: >>> templating.render('Hello', '.silly', name="test") Traceback (most recent call last): ... KeyError: '.silly' We now want to plug into this filehandler framework and provide a handler for ``.silly`` files. Since we are writing a plugin, we cannot change the ``templating`` module directly. Let's write an extension module instead:: >>> class sillytemplating(FakeModule): ... class SillyTemplate(object): ... "Replace {key} with dictionary values." ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... text = self.text ... for key, value in kw.items(): ... text = text.replace('{%s}' % key, value) ... return text ... ... templating.extension_handlers['.silly'] = SillyTemplate >>> from martiantest.fake import sillytemplating In the extension module, we manipulate the ``extension_handlers`` dictionary of the ``templating`` module (in normal code we'd need to import it first), and plug in our own function. ``.silly`` handling works now:: >>> templating.render('Hello {name}!', '.silly', name="galaxy") 'Hello galaxy!' Above we plug into our ``extension_handler`` registry using Python code. Using separate code to manually hook components into registries can get rather cumbersome - each time you write a plugin, you also need to remember you need to register it. Doing template registration in Python code also poses a maintenance risk. It is tempting to start doing fancy things in Python code such as conditional configuration, making the configuration state of a program hard to understand. Another problem is that doing configuration at import time can also lead to unwanted side effects during import, as well as ordering problems, where you want to import something that really needs configuration state in another module that is imported later. Finally, it can also make code harder to test, as configuration is loaded always when you import the module, even if in your test perhaps you don't want it to be. Martian provides a framework that allows configuration to be expressed in declarative Python code. Martian is based on the realization that what to configure where can often be deduced from the structure of Python code itself, especially when it can be annotated with additional declarations. The idea is to make it so easy to write and register a plugin so that even extensive configuration does not overly burden the developer. Configuration actions are executed during a separate phase ("grok time"), not at import time, which makes it easier to reason about and easier to test. Configuration the Martian Way ============================= Let's now transform the above ``templating`` module and the ``sillytemplating`` module to use Martian. First we must recognize that every template language is configured to work for a particular extension. With Martian, we annotate the classes themselves with this configuration information. Annotations happen using *directives*, which look like function calls in the class body. Let's create an ``extension`` directive that can take a single string as an argument, the file extension to register the template class for:: >>> import martian >>> class extension(martian.Directive): ... scope = martian.CLASS ... store = martian.ONCE ... default = None We also need a way to easily recognize all template classes. The normal pattern for this in Martian is to use a base class, so let's define a ``Template`` base class:: >>> class Template(object): ... pass We now have enough infrastructure to allow us to change the code to use Martian style base class and annotations:: >>> class templating(FakeModule): ... ... class InterpolationTemplate(Template): ... "Use %(foo)s for dictionary interpolation." ... extension('.txt') ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... return self.text % kw ... ... class TemplateStringTemplate(Template): ... "PEP 292 string substitutions." ... extension('.tmpl') ... def __init__(self, text): ... self.template = string.Template(text) ... def render(self, **kw): ... return self.template.substitute(**kw) ... ... # the registry, empty to start with ... extension_handlers = {} ... ... def render(data, extension, **kw): ... # this hasn't changed ... template = extension_handlers[extension](data) ... return template.render(**kw) >>> from martiantest.fake import templating As you can see, there have been very few changes: * we made the template classes inherit from ``Template``. * we use the ``extension`` directive in the template classes. * we stopped pre-filling the ``extension_handlers`` dictionary. So how do we fill the ``extension_handlers`` dictionary with the right template languages? Now we can use Martian. We define a *grokker* for ``Template`` that registers the template classes in the ``extension_handlers`` registry:: >>> class meta(FakeModule): ... class TemplateGrokker(martian.ClassGrokker): ... martian.component(Template) ... martian.directive(extension) ... def execute(self, class_, extension, **kw): ... templating.extension_handlers[extension] = class_ ... return True >>> from martiantest.fake import meta What does this do? A ``ClassGrokker`` has its ``execute`` method called for subclasses of what's indicated by the ``martian.component`` directive. You can also declare what directives a ``ClassGrokker`` expects on this component by using ``martian.directive()`` (the ``directive`` directive!) one or more times. The ``execute`` method takes the class to be grokked as the first argument, and the values of the directives used will be passed in as additional parameters into the ``execute`` method. The framework can also pass along an arbitrary number of extra keyword arguments during the grokking process, so we need to declare ``**kw`` to make sure we can handle these. All our grokkers will be collected in a special Martian-specific registry:: >>> reg = martian.GrokkerRegistry() We will need to make sure the system is aware of the ``TemplateGrokker`` defined in the ``meta`` module first, so let's register it first. We can do this by simply grokking the ``meta`` module:: >>> reg.grok('meta', meta) True Because ``TemplateGrokker`` is now registered, our registry now knows how to grok ``Template`` subclasses. Let's grok the ``templating`` module:: >>> reg.grok('templating', templating) True Let's try the ``render`` function of templating again, to demonstrate we have successfully grokked the template classes:: >>> templating.render('Hello %(name)s!', '.txt', name="world") 'Hello world!' >>> templating.render('Hello ${name}!', '.tmpl', name="universe") 'Hello universe!' ``.silly`` hasn't been registered yet:: >>> templating.render('Hello', '.silly', name="test") Traceback (most recent call last): ... KeyError: '.silly' Let's now register ``.silly`` from an extension module:: >>> class sillytemplating(FakeModule): ... class SillyTemplate(Template): ... "Replace {key} with dictionary values." ... extension('.silly') ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... text = self.text ... for key, value in kw.items(): ... text = text.replace('{%s}' % key, value) ... return text >>> from martiantest.fake import sillytemplating As you can see, the developer that uses the framework has no need anymore to know about ``templating.extension_handlers``. Instead we can simply grok the module to have ``SillyTemplate`` be register appropriately:: >>> reg.grok('sillytemplating', sillytemplating) True We can now use the ``.silly`` templating engine too:: >>> templating.render('Hello {name}!', '.silly', name="galaxy") 'Hello galaxy!' Admittedly it is hard to demonstrate Martian well with a small example like this. In the end we have actually written more code than in the basic framework, after all. But even in this small example, the ``templating`` and ``sillytemplating`` module have become more declarative in nature. The developer that uses the framework will not need to know anymore about things like ``templating.extension_handlers`` or an API to register things there. Instead the developer can registering a new template system anywhere, as long as he subclasses from ``Template``, and as long as his code is grokked by the system. Finally note how Martian was used to define the ``TemplateGrokker`` as well. In this way Martian can use itself to extend itself. Grokking instances ================== Above we've seen how you can grok classes. Martian also supplies a way to grok instances. This is less common in typical frameworks, and has the drawback that no class-level directives can be used, but can still be useful. Let's imagine a case where we have a zoo framework with an ``Animal`` class, and we want to track instances of it:: >>> class Animal(object): ... def __init__(self, name): ... self.name = name >>> class zoo(FakeModule): ... horse = Animal('horse') ... chicken = Animal('chicken') ... elephant = Animal('elephant') ... lion = Animal('lion') ... animals = {} >>> from martiantest.fake import zoo We define an ``InstanceGrokker`` subclass to grok ``Animal`` instances:: >>> class meta(FakeModule): ... class AnimalGrokker(martian.InstanceGrokker): ... martian.component(Animal) ... def execute(self, instance, **kw): ... zoo.animals[instance.name] = instance ... return True >>> from martiantest.fake import meta Let's create a new registry with the ``AnimalGrokker`` in it:: >>> reg = martian.GrokkerRegistry() >>> reg.grok('meta', meta) True We can now grok the ``zoo`` module:: >>> reg.grok('zoo', zoo) True The animals will now be in the ``animals`` dictionary:: >>> sorted(zoo.animals.items()) [('chicken', ), ('elephant', ), ('horse', ), ('lion', )] More information ================ For many more details and examples of more kinds of grokkers, please see ``src/martian/core.txt``. For more information on directives see ``src/martian/directive.txt``. martian-0.14/src/martian/testing.py0000644000175000017500000000404411464246706015200 0ustar jwjwimport new import sys def fake_import(fake_module): module_name = 'martiantest.fake.' + fake_module.__name__ module = new.module(module_name) module_name_parts = module_name.split('.') module.__file__ = '/' + '/'.join(module_name_parts) glob = {} for name in dir(fake_module): if name.startswith('__') and '.' not in name: continue obj = getattr(fake_module, name) glob[name] = obj try: obj = obj.im_func except AttributeError: pass __module__ = None try: __module__ == obj.__dict__.get('__module__') except AttributeError: try: __module__ = obj.__module__ except AttributeError: pass if __module__ is None or __module__ == '__builtin__': try: obj.__module__ = module.__name__ except AttributeError: pass setattr(module, name, obj) # provide correct globals for functions for name in dir(module): if name.startswith('__'): continue obj = getattr(module, name) try: code = obj.func_code new_func = new.function(code, glob, name) new_func.__module__ = module.__name__ setattr(module, name, new_func) glob[name] = new_func except AttributeError: pass if not 'martiantest' in sys.modules: sys.modules['martiantest'] = new.module('martiantest') sys.modules['martiantest.fake'] = new.module('martiantest.fake') sys.modules['martiantest'].fake = sys.modules['martiantest.fake'] sys.modules[module_name] = module setattr(sys.modules['martiantest.fake'], module_name.split('.')[-1], module) return module class FakeModuleMetaclass(type): def __init__(cls, classname, bases, dict_): fake_import(cls) return type.__init__(cls, classname, bases, dict_) class FakeModule(object): __metaclass__ = FakeModuleMetaclass martian-0.14/src/martian/directive.py0000644000175000017500000002726011464246706015506 0ustar jwjwimport sys import inspect from zope.interface.interfaces import IInterface from zope.interface.interface import TAGGED_DATA from martian import util from martian.error import GrokImportError, GrokError from martian import scan UNKNOWN = object() class StoreOnce(object): def set(self, locals_, directive, value): if directive.dotted_name() in locals_: raise GrokImportError( "The '%s' directive can only be called once per %s." % (directive.name, directive.scope.description)) locals_[directive.dotted_name()] = value def get(self, directive, component, default): return getattr(component, directive.dotted_name(), default) def setattr(self, context, directive, value): setattr(context, directive.dotted_name(), value) ONCE = StoreOnce() class StoreOnceGetFromThisClassOnly(StoreOnce): def get(self, directive, component, default): return component.__dict__.get(directive.dotted_name(), default) ONCE_NOBASE = StoreOnceGetFromThisClassOnly() class StoreMultipleTimes(StoreOnce): def get(self, directive, component, default): if getattr(component, directive.dotted_name(), default) is default: return default if getattr(component, 'mro', None) is None: return getattr(component, directive.dotted_name()) result = [] for base in reversed(component.mro()): list = getattr(base, directive.dotted_name(), default) if list is not default and list not in result: result.append(list) result_flattened = [] for entry in result: result_flattened.extend(entry) return result_flattened def set(self, locals_, directive, value): values = locals_.setdefault(directive.dotted_name(), []) values.append(value) MULTIPLE = StoreMultipleTimes() class StoreMultipleTimesNoBase(StoreMultipleTimes): def get(self, directive, component, default): return component.__dict__.get(directive.dotted_name(), default) MULTIPLE_NOBASE = StoreMultipleTimesNoBase() class StoreDict(StoreOnce): def get(self, directive, component, default): if getattr(component, directive.dotted_name(), default) is default: return default if getattr(component, 'mro', None) is None: return getattr(component, directive.dotted_name()) result = {} for base in reversed(component.mro()): mapping = getattr(base, directive.dotted_name(), default) if mapping is not default: result.update(mapping) return result def set(self, locals_, directive, value): values_dict = locals_.setdefault(directive.dotted_name(), {}) try: key, value = value except (TypeError, ValueError): raise GrokImportError( "The factory method for the '%s' directive should return a " "key-value pair." % directive.name) values_dict[key] = value DICT = StoreDict() class TaggedValueStoreOnce(StoreOnce): """Stores the directive value in a interface tagged value. """ def get(self, directive, component, default): return component.queryTaggedValue(directive.dotted_name(), default) def set(self, locals_, directive, value): if directive.dotted_name() in locals_: raise GrokImportError( "The '%s' directive can only be called once per %s." % (directive.name, directive.scope.description)) # Make use of the implementation details of interface tagged # values. Instead of being able to call "setTaggedValue()" # on an interface object, we only have access to the "locals" # of the interface object. We inject whatever setTaggedValue() # would've injected. taggeddata = locals_.setdefault(TAGGED_DATA, {}) taggeddata[directive.dotted_name()] = value def setattr(self, context, directive, value): context.setTaggedValue(directive.dotted_name(), value) # for now, use scope = martian.CLASS to create directives that can # work on interfaces (or martian.CLASS_OR_MODULE) ONCE_IFACE = TaggedValueStoreOnce() _USE_DEFAULT = object() class UnknownError(GrokError): pass def _default(mro, get_default): """Apply default rule to list of classes in mro. """ error = None for base in mro: module_of_base = scan.resolve(base.__module__) try: if util.is_baseclass(base): break result = get_default(base, module_of_base) except UnknownError, e: # store error if this is the first UnknownError we ran into if error is None: error = e result = UNKNOWN if result is not UNKNOWN: return result # if we haven't found a result, raise the first error we had as # a GrokError if error is not None: raise GrokError(unicode(error), error.component) return UNKNOWN class ClassScope(object): description = 'class' def check(self, frame): return util.frame_is_class(frame) and not is_fake_module(frame) def get(self, directive, component, get_default): result = directive.store.get(directive, component, _USE_DEFAULT) if result is not _USE_DEFAULT: return result # We may be really dealing with an instance instead of a class. if not util.isclass(component): component = component.__class__ return _default(inspect.getmro(component), get_default) CLASS = ClassScope() class ClassOrModuleScope(object): description = 'class or module' def check(self, frame): return util.frame_is_class(frame) or util.frame_is_module(frame) def get(self, directive, component, get_default): # look up class-level directive on this class or its bases # we don't need to loop through the __mro__ here as Python will # do it for us result = directive.store.get(directive, component, _USE_DEFAULT) if result is not _USE_DEFAULT: return result # we may be really dealing with an instance or a module here if not util.isclass(component): return get_default(component, component) # now we need to loop through the mro, potentially twice mro = inspect.getmro(component) # look up module-level directive for this class or its bases for base in mro: module_of_base = scan.resolve(base.__module__) result = directive.store.get(directive, module_of_base, _USE_DEFAULT) if result is not _USE_DEFAULT: return result # look up default rule for this class or its bases return _default(mro, get_default) CLASS_OR_MODULE = ClassOrModuleScope() class ModuleScope(object): description = 'module' def check(self, frame): return util.frame_is_module(frame) or is_fake_module(frame) def get(self, directive, component, get_default): result = directive.store.get(directive, component, _USE_DEFAULT) if result is not _USE_DEFAULT: return result return get_default(component, component) MODULE = ModuleScope() _unused = object() class Directive(object): # The BoundDirective will fallback to the directive-level default value. default = None def __init__(self, *args, **kw): self.name = self.__class__.__name__ self.frame = frame = sys._getframe(1) if not self.scope.check(frame): raise GrokImportError("The '%s' directive can only be used on " "%s level." % (self.name, self.scope.description)) self.check_factory_signature(*args, **kw) validate = getattr(self, 'validate', None) if validate is not None: validate(*args, **kw) value = self.factory(*args, **kw) self.store.set(frame.f_locals, self, value) # To get a correct error message, we construct a function that has # the same signature as factory(), but without "self". def check_factory_signature(self, *arguments, **kw): args, varargs, varkw, defaults = inspect.getargspec(self.factory) argspec = inspect.formatargspec(args[1:], varargs, varkw, defaults) exec("def signature_checker" + argspec + ": pass") try: signature_checker(*arguments, **kw) except TypeError, e: message = e.args[0] message = message.replace("signature_checker()", self.name) raise TypeError(message) def factory(self, value): return value @classmethod def dotted_name(cls): return cls.__module__ + '.' + cls.__name__ @classmethod def set(cls, component, value): cls.store.setattr(component, cls, value) @classmethod def bind(cls, default=_unused, get_default=None, name=None): return BoundDirective(cls, default, get_default, name) class BoundDirective(object): def __init__(self, directive, default=_unused, get_default=None, name=None): self.directive = directive self.default = default if name is None: name = directive.__name__ self.name = name # Whenever the requester provides its own get_default function, # it'll override the default get_default. if get_default is not None: self.get_default = get_default def get_default(self, component, module=None, **data): if self.default is not _unused: return self.default # Fallback to the directive-level default value. Call the # ``get_default`` classmethod when it is available, else use the # ``default`` attribute. if hasattr(self.directive, 'get_default'): return self.directive.get_default(component, module, **data) return self.directive.default def get(self, component=None, module=None, **data): directive = self.directive def get_default(component, module): return self.get_default(component, module, **data) return directive.scope.get(directive, component, get_default=get_default) class MultipleTimesDirective(Directive): store = MULTIPLE default = [] class MarkerDirective(Directive): store = ONCE default = False def factory(self): return True def validateText(directive, value): if util.not_unicode_or_ascii(value): raise GrokImportError("The '%s' directive can only be called with " "unicode or ASCII." % directive.name) def validateInterfaceOrClass(directive, value): if not (IInterface.providedBy(value) or util.isclass(value)): raise GrokImportError("The '%s' directive can only be called with " "a class or an interface." % directive.name) def validateClass(directive, value): if not util.isclass(value): raise GrokImportError("The '%s' directive can only be called with " "a class." % directive.name) def validateInterface(directive, value): if not (IInterface.providedBy(value)): raise GrokImportError("The '%s' directive can only be called with " "an interface." % directive.name) # this here only for testing purposes, which is a bit unfortunate # but makes the tests a lot clearer for module-level directives # also unfortunate that fake_module needs to be defined directly # in the fake module being tested and not in the FakeModule base class; # the system cannot find it on the frame if it is in the base class. def is_fake_module(frame): return frame.f_locals.has_key('fake_module') martian-0.14/src/martian/martiandirective.py0000644000175000017500000000203211464246706017050 0ustar jwjw"""Martian-specific directives""" from martian.directive import (Directive, MultipleTimesDirective, MarkerDirective, validateClass, CLASS, ONCE, ONCE_NOBASE) from martian.error import GrokImportError class component(Directive): scope = CLASS store = ONCE default = None validate = validateClass class directive(MultipleTimesDirective): scope = CLASS def validate(self, directive, *args, **kw): try: if issubclass(directive, Directive): return except TypeError: # directive is not a class, so error too pass raise GrokImportError("The '%s' directive can only be called with " "a directive." % self.name) def factory(self, directive, *args, **kw): return directive.bind(*args, **kw) class priority(Directive): scope = CLASS store = ONCE default = 0 class baseclass(MarkerDirective): scope = CLASS store = ONCE_NOBASE martian-0.14/src/martian/error.py0000644000175000017500000000150511464246706014653 0ustar jwjw############################################################################## # # Copyright (c) 2006-2007 Zope Foundation 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 GrokError(Exception): def __init__(self, message, component): Exception.__init__(self, message) self.component = component class GrokImportError(ImportError): pass martian-0.14/src/martian/__init__.py0000644000175000017500000000154711464246706015267 0ustar jwjwfrom martian.core import ( ModuleGrokker, MultiGrokker, MetaMultiGrokker, grok_dotted_name, grok_package, grok_module, GrokkerRegistry) from martian.components import GlobalGrokker, ClassGrokker, InstanceGrokker from martian.components import MethodGrokker from martian.util import scan_for_classes from martian.directive import Directive, MarkerDirective, MultipleTimesDirective from martian.directive import (ONCE, ONCE_NOBASE, ONCE_IFACE, MULTIPLE, MULTIPLE_NOBASE, DICT) from martian.directive import CLASS, CLASS_OR_MODULE, MODULE from martian.directive import UNKNOWN, UnknownError from martian.directive import ( validateText, validateInterface, validateClass, validateInterfaceOrClass) from martian.martiandirective import component, directive, priority, baseclass from martian.context import GetDefaultComponentFactory martian-0.14/src/martian/util.py0000644000175000017500000001020311464246706014472 0ustar jwjw############################################################################## # # Copyright (c) 2006-2007 Zope Foundation 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. # ############################################################################## """Martian utility functions. """ import re import types import sys import inspect from zope import interface import martian from martian.error import GrokError, GrokImportError def not_unicode_or_ascii(value): if isinstance(value, unicode): return False if not isinstance(value, str): return True return is_not_ascii(value) is_not_ascii = re.compile(eval(r'u"[\u0080-\uffff]"')).search def isclass(obj): """We cannot use ``inspect.isclass`` because it will return True for interfaces""" return isinstance(obj, (types.ClassType, type)) def check_subclass(obj, class_): if not isclass(obj): return False return issubclass(obj, class_) def caller_module(): return sys._getframe(2).f_globals['__name__'] def is_baseclass(component): # this is a bit more low-level than we really want, but # using martian.baseclass.bind().get(component) has the unfortunate # site effect of causing infinite recursion when using this within # the implementation of directives, which we now do... return (isclass(component) and 'martian.martiandirective.baseclass' in component.__dict__) def defined_locally(obj, dotted_name): obj_module = getattr(obj, '__grok_module__', None) if obj_module is None: obj_module = getattr(obj, '__module__', None) return obj_module == dotted_name def check_implements_one(class_): check_implements_one_from_list(list(interface.implementedBy(class_)), class_) def check_implements_one_from_list(list, class_): if len(list) < 1: raise GrokError("%r must implement at least one interface " "(use grok.implements to specify)." % class_, class_) elif len(list) > 1: raise GrokError("%r is implementing more than one interface " "(use grok.provides to specify which one to use)." % class_, class_) def check_provides_one(obj): provides = list(interface.providedBy(obj)) if len(provides) < 1: raise GrokError("%r must provide at least one interface " "(use zope.interface.classProvides to specify)." % obj, obj) if len(provides) > 1: raise GrokError("%r provides more than one interface " "(use grok.provides to specify which one to use)." % obj, obj) def scan_for_classes(module, iface): """Given a module, scan for classes. """ for name in dir(module): if '.' in name: # This must be a module-level variable that couldn't have # been set by the developer. It must have been a module-level # directive. continue obj = getattr(module, name) if not defined_locally(obj, module.__name__) or not isclass(obj): continue if iface.implementedBy(obj): yield obj def methods_from_class(class_): # XXX Problem with zope.interface here that makes us special-case # __provides__. candidates = [getattr(class_, name) for name in dir(class_) if name != '__provides__' ] methods = [c for c in candidates if inspect.ismethod(c)] return methods def public_methods_from_class(class_): return [m for m in methods_from_class(class_) if \ not m.__name__.startswith('_')] def frame_is_module(frame): return frame.f_locals is frame.f_globals def frame_is_class(frame): return '__module__' in frame.f_locals martian-0.14/src/martian/edgecase.txt0000644000175000017500000002303011464246706015446 0ustar jwjw(Edge-case) tests of Martian core components ============================================ ModuleGrokker ignores values set by directives ---------------------------------------------- Consider the following module-level directive: >>> import martian >>> class store(martian.Directive): ... scope = martian.MODULE ... store = martian.ONCE ... >>> store.__module__ = 'somethingelse' # just so that it isn't __builtin__ Now let's look at a module that contains a simple function and a call to the directive defined above: >>> class module_with_directive(FakeModule): ... fake_module = True ... ... def some_function(): ... return 11 ... ... store(some_function) ... >>> from martiantest.fake import module_with_directive Now imagine we have the following grokker for functions: >>> import types >>> class FunctionGrokker(martian.InstanceGrokker): ... martian.component(types.FunctionType) ... def grok(self, name, obj, **kw): ... print name, obj() ... return True ... >>> module_grokker = martian.ModuleGrokker() >>> module_grokker.register(FunctionGrokker()) and let it loose on the module, we see that it will only find functions set by regular variable assignment, not the ones stored by the directive: >>> module_grokker.grok('module_with_directive', module_with_directive) some_function 11 True Directive scope and default edge cases -------------------------------------- >>> from martian import Directive, CLASS_OR_MODULE, CLASS, MODULE >>> from martian import ONCE MODULE scope directive on a module, with no explicit value:: >>> class mydir(martian.Directive): ... scope = MODULE ... store = ONCE >>> class module_no_explicit_value(FakeModule): ... fake_module = True >>> from martiantest.fake import module_no_explicit_value >>> mydir.bind().get(module_no_explicit_value) is None True MODULE scope directive on a module, with an explicit value:: >>> class mydir2(martian.Directive): ... scope = MODULE ... store = ONCE >>> class module_with_explicit_value(FakeModule): ... fake_module = True ... mydir2('the explicit value') >>> from martiantest.fake import module_with_explicit_value >>> mydir2.bind().get(module_with_explicit_value) 'the explicit value' MODULE scope directive on a module, with no explicit value, with a custom default:: >>> class mydir(martian.Directive): ... scope = MODULE ... store = ONCE >>> class module_custom_default(FakeModule): ... fake_module = True >>> from martiantest.fake import module_custom_default >>> def custom(component, module, **data): ... return 'a custom default value' >>> mydir.bind(get_default=custom).get(module_custom_default) 'a custom default value' CLASS scope directive on a class, with no explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS ... store = ONCE >>> class module_get_from_class_no_explicit(FakeModule): ... class MyClass(object): ... pass >>> from martiantest.fake import module_get_from_class_no_explicit >>> mydir.bind().get(module_get_from_class_no_explicit.MyClass) is None True CLASS scope directive on an instance, with no explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS ... store = ONCE >>> class module_get_from_instance_no_explicit(FakeModule): ... class MyClass(object): ... pass ... obj = MyClass() >>> from martiantest.fake import module_get_from_instance_no_explicit >>> mydir.bind().get(module_get_from_instance_no_explicit.obj) is None True CLASS scope directive on a class, with an explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS ... store = ONCE >>> class module_get_from_class_with_explicit(FakeModule): ... class MyClass(object): ... mydir('explicitly set') >>> from martiantest.fake import module_get_from_class_with_explicit >>> mydir.bind().get(module_get_from_class_with_explicit.MyClass) 'explicitly set' CLASS scope directive on an instance, with an explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS ... store = ONCE >>> class module_get_from_instance_with_explicit(FakeModule): ... class MyClass(object): ... mydir('explicitly set') ... obj = MyClass() >>> from martiantest.fake import module_get_from_instance_with_explicit >>> mydir.bind().get(module_get_from_instance_with_explicit.obj) 'explicitly set' CLASS scope directive on a class, with a custom default:: >>> class mydir(martian.Directive): ... scope = CLASS ... store = ONCE >>> class module_get_from_class_with_custom(FakeModule): ... class MyClass(object): ... pass >>> from martiantest.fake import module_get_from_class_with_custom >>> def custom_get_default(component, module, **data): ... return 'custom default' >>> mydir.bind(get_default=custom_get_default).get( ... module_get_from_class_with_custom.MyClass) 'custom default' CLASS scope directive on an instance, with a custom default:: >>> class mydir(martian.Directive): ... scope = CLASS ... store = ONCE >>> class module_get_from_instance_with_custom(FakeModule): ... class MyClass(object): ... pass ... obj = MyClass() >>> from martiantest.fake import module_get_from_instance_with_custom >>> mydir.bind(get_default=custom_get_default).get( ... module_get_from_instance_with_custom.obj) 'custom default' CLASS_OR_MODULE scope directive on a module, with no explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module(FakeModule): ... fake_module = True ... pass >>> from martiantest.fake import module >>> mydir.bind().get(module) is None True CLASS_OR_MODULE scope directive on a class, with no explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module(FakeModule): ... fake_module = True ... class MyClass(object): ... pass >>> from martiantest.fake import module >>> mydir.bind().get(module.MyClass) is None True CLASS_OR_MODULE scope directive on an instance, with no explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module(FakeModule): ... fake_module = True ... class MyClass(object): ... pass ... obj = MyClass() >>> from martiantest.fake import module >>> mydir.bind().get(module.obj) is None True CLASS_OR_MODULE scope directive on a module, with an explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module(FakeModule): ... fake_module = True ... mydir('explicitly set, see?') >>> from martiantest.fake import module >>> mydir.bind().get(module) 'explicitly set, see?' CLASS_OR_MODULE scope directive on a class, with an explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module(FakeModule): ... fake_module = True ... class MyClass(object): ... mydir('explicitly set, see?') >>> from martiantest.fake import module >>> mydir.bind().get(module.MyClass) 'explicitly set, see?' CLASS_OR_MODULE scope directive on an instance, with an explicit value:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module(FakeModule): ... fake_module = True ... class MyClass(object): ... mydir('explicitly set, see?') ... obj = MyClass() >>> from martiantest.fake import module >>> mydir.bind().get(module.obj) 'explicitly set, see?' CLASS_OR_MODULE scope directive on a module, with a custom default:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module(FakeModule): ... fake_module = True >>> from martiantest.fake import module >>> mydir.bind(get_default=custom_get_default).get(module) 'custom default' CLASS_OR_MODULE scope directive on a class, with a custom default:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module(FakeModule): ... fake_module = True ... class MyClass(object): ... pass >>> from martiantest.fake import module >>> mydir.bind(get_default=custom_get_default).get(module.MyClass) 'custom default' CLASS_OR_MODULE scope directive on an instance, with a custom default:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module(FakeModule): ... fake_module = True ... class MyClass(object): ... pass ... obj = MyClass() >>> from martiantest.fake import module >>> mydir.bind(get_default=custom_get_default).get(module.obj) 'custom default' CLASS_OR_MODULE scope directive on the module, with inheritance:: >>> class mydir(martian.Directive): ... scope = CLASS_OR_MODULE ... store = ONCE >>> class module_b(FakeModule): ... fake_module = True ... mydir('a value') ... class B(object): ... pass >>> from martiantest.fake import module_b >>> class module_a(FakeModule): ... fake_module = True ... class A(module_b.B): ... pass >>> from martiantest.fake import module_a >>> mydir.bind(get_default=custom_get_default).get(module_a.A) 'a value' martian-0.14/src/martian/interfaces.py0000644000175000017500000000643711464246706015656 0ustar jwjw############################################################################## # # Copyright (c) 2006-2007 Zope Foundation 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. # ############################################################################## from zope.interface import Interface, Attribute class IGrokker(Interface): """A grokker that groks components. Use the martian.priority directive to specify the priority (within a module) with which to grok. The higher the priority, the earlier the grokker will be executed. """ def grok(name, obj, **kw): """Grok obj. name - name of object (in module) obj - object to grok **kw - optional parameters passed along the grokking process. May do extra filtering based on name or obj. Returns True if grok is attempted, False if object is filtered out by this grokker. """ class IComponentGrokker(IGrokker): """A grokker that groks components in a module. Use the martian.component directive to specify the component to grok. """ class IMultiGrokker(IComponentGrokker): """A grokker that is composed out of multiple grokkers. """ def register(grokker): """Register a grokker. """ def clear(): """Clear all grokkers and go back to initial state. """ def grokkers(name, obj): """Iterable of all grokkers that apply to obj. """ class IModuleInfo(Interface): def getModule(): """Get the module object this module info is representing. In case of packages, gives back the module object for the package's __init__.py """ def isPackage(): """Returns True if this module is a package. """ def getSubModuleInfos(): """Get module infos for any sub modules. In a module, this will always return an empty list. """ def getSubModuleInfo(name): """Get sub module info for . In a package, give module info for sub-module. Returns None if no such sub module is found. In a module, always returns None. """ def getResourcePath(name): """Get the absolute path of a resource directory. The resource directory will be 'relative' to this package or module. Case one: get the resource directory with name from the same directory as this module Case two: get the resource directory with name from the children of this package """ def getAnnotation(key, default): """Given a key, get annotation object from module. The annotation key is a dotted name. All dots are replaced with underscores and the result is pre and post-fixed by double underscore. For instance 'grok.name' will be translated to '__grok_name__'. Uses default if no such annotation found. """ martian-0.14/src/martian/scan.py0000644000175000017500000001706211464246706014453 0ustar jwjw############################################################################## # # Copyright (c) 2006-2007 Zope Foundation 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. # ############################################################################## """Scanning packages and modules """ import os from zope.interface import implements from martian.interfaces import IModuleInfo def is_package(path): if not os.path.isdir(path): return False init_py = os.path.join(path, '__init__.py') init_pyc = init_py + 'c' # Check whether either __init__.py or __init__.pyc exist return os.path.isfile(init_py) or os.path.isfile(init_pyc) class ModuleInfo(object): implements(IModuleInfo) def __init__(self, path, dotted_name, exclude_filter=None, ignore_nonsource=True): # Normalize .pyc files to .py if path.endswith('c'): path = path[:-1] self.path = path self.dotted_name = dotted_name self.ignore_nonsource = ignore_nonsource if exclude_filter is None: # this exclude filter receives extensionless filenames self.exclude_filter = lambda x: False else: self.exclude_filter = exclude_filter name_parts = dotted_name.split('.') self.name = name_parts[-1] if self.isPackage(): self.package_dotted_name = dotted_name else: self.package_dotted_name = '.'.join(name_parts[:-1]) self._module = None def getResourcePath(self, name): """Get the absolute path of a resource directory 'relative' to this package or module. Case one: get the resource directory with name from the same directory as this module Case two: get the resource directory with name from the children of this package """ return os.path.join(os.path.dirname(self.path), name) def getSubModuleInfos(self): if not self.isPackage(): return [] directory = os.path.dirname(self.path) module_infos = [] seen = [] for entry in sorted(os.listdir(directory)): # we are only interested in things that are potentially # python modules or packages, and therefore start with a # letter or _ if not entry[0].isalpha() and entry[0] != '_': continue entry_path = os.path.join(directory, entry) name, ext = os.path.splitext(entry) dotted_name = self.dotted_name + '.' + name if self.exclude_filter(name) or name == '__main__': continue if self.ignore_nonsource: if ext in ['.pyo', '.pyc']: continue # Case one: modules if (os.path.isfile(entry_path) and ext in ['.py', '.pyc']): if name == '__init__': continue # Avoid duplicates when both .py and .pyc exist if name in seen: continue seen.append(name) module_infos.append( ModuleInfo(entry_path, dotted_name, exclude_filter=self.exclude_filter, ignore_nonsource=self.ignore_nonsource) ) # Case two: packages elif is_package(entry_path): # We can blindly use __init__.py even if only # __init__.pyc exists because we never actually use # that filename. module_infos.append(ModuleInfo( os.path.join(entry_path, '__init__.py'), dotted_name, exclude_filter=self.exclude_filter, ignore_nonsource=self.ignore_nonsource)) return module_infos def getSubModuleInfo(self, name): path = os.path.join(os.path.dirname(self.path), name) if is_package(path): return ModuleInfo(os.path.join(path, '__init__.py'), '%s.%s' % (self.package_dotted_name, name), exclude_filter=self.exclude_filter, ignore_nonsource=self.ignore_nonsource) elif os.path.isfile(path + '.py') or os.path.isfile(path + '.pyc'): return ModuleInfo(path + '.py', '%s.%s' % (self.package_dotted_name, name), exclude_filter=self.exclude_filter, ignore_nonsource = self.ignore_nonsource) else: return None def getAnnotation(self, key, default): key = key.replace('.', '_') key = '__%s__' % key module = self.getModule() return getattr(module, key, default) def getModule(self): if self._module is None: self._module = resolve(self.dotted_name) return self._module def isPackage(self): return self.path.endswith('__init__.py') def __repr__(self): return "" % self.dotted_name class BuiltinDummyModule(object): """Needed for BuiltinModuleInfo""" pass class BuiltinModuleInfo(object): implements(IModuleInfo) # to let view grokking succeed in tests package_dotted_name = 'dummy.dotted.name' def getModule(self): return BuiltinDummyModule() def isPackage(self): return False def getSubModuleInfos(self): return [] def getSubModuleInfo(self, name): return None def getResourcePath(self, name): # XXX we break the contract here. We could return # a link to some temp directory for testing purposes? return None def getAnnotation(self, key, default): return default def module_info_from_dotted_name(dotted_name, exclude_filter=None, ignore_nonsource=True): if dotted_name == '__builtin__': # in case of the use of individually grokking something during a # test the dotted_name being passed in could be __builtin__ # in this case we return a special ModuleInfo that just # implements enough interface to work return BuiltinModuleInfo() module = resolve(dotted_name) return ModuleInfo(module.__file__, dotted_name, exclude_filter, ignore_nonsource) def module_info_from_module(module, exclude_filter=None, ignore_nonsource=True): return ModuleInfo(module.__file__, module.__name__, exclude_filter, ignore_nonsource) # taken from zope.dottedname.resolve def resolve(name, module=None): name = name.split('.') if not name[0]: if module is None: raise ValueError("relative name without base module") module = module.split('.') name.pop(0) while not name[0]: module.pop() name.pop(0) name = module + name used = name.pop(0) found = __import__(used) for n in name: used += '.' + n try: found = getattr(found, n) except AttributeError: __import__(used) found = getattr(found, n) return found martian-0.14/src/martian.egg-info/0000755000175000017500000000000011464246723014640 5ustar jwjwmartian-0.14/src/martian.egg-info/SOURCES.txt0000644000175000017500000000523611464246720016527 0ustar jwjwCHANGES.txt COPYRIGHT.txt CREDITS.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/martian/README.txt src/martian/__init__.py src/martian/components.py src/martian/context.py src/martian/context.txt src/martian/core.py src/martian/core.txt src/martian/directive.py src/martian/directive.txt src/martian/edgecase.txt src/martian/error.py src/martian/interfaces.py src/martian/martiandirective.py src/martian/scan.py src/martian/scan.txt src/martian/testing.py src/martian/util.py src/martian.egg-info/PKG-INFO src/martian.egg-info/SOURCES.txt src/martian.egg-info/dependency_links.txt src/martian.egg-info/not-zip-safe src/martian.egg-info/requires.txt src/martian.egg-info/top_level.txt src/martian/tests/__init__.py src/martian/tests/public_methods_from_class.txt src/martian/tests/scan_for_classes.txt src/martian/tests/test_all.py src/martian/tests/scanforclasses/__init__.py src/martian/tests/scanforclasses/test1.py src/martian/tests/scanforclasses/test2.py src/martian/tests/scanforclasses/test3.py src/martian/tests/scanforclasses/test4.py src/martian/tests/stoneage/__init__.py src/martian/tests/stoneage/cave.py src/martian/tests/stoneage/hunt/__init__.py src/martian/tests/stoneage/hunt/mammoth.py src/martian/tests/stoneage/hunt/mammoth_templates/index.pt src/martian/tests/stoneage/notpackage/dummy.py src/martian/tests/stoneage/painting/__init__.py src/martian/tests/testpackage/__init__.py src/martian/tests/testpackage/animal.py src/martian/tests/testpackage/one.py src/martian/tests/testpackage/two.py src/martian/tests/testpackage/alpha/__init__.py src/martian/tests/testpackage/beta/__init__.py src/martian/tests/testpackage/beta/three.py src/martian/tests/with__main__/__init__.py src/martian/tests/with__main__/__main__.py src/martian/tests/with__main__/package.py src/martian/tests/withbogusmodules/.bogus.py src/martian/tests/withbogusmodules/1alsobogus.py src/martian/tests/withbogusmodules/__init__.py src/martian/tests/withbogusmodules/nonbogus.py src/martian/tests/withbogusmodules/.bogussubpackage/__init__.py src/martian/tests/withbogusmodules/subpackage/__init__.py src/martian/tests/withpyconly/__init__.py src/martian/tests/withpyconly/foo.py src/martian/tests/withpyconly/subpackage/__init__.py src/martian/tests/withtestsmodules/__init__.py src/martian/tests/withtestsmodules/ftests.py src/martian/tests/withtestsmodules/tests.py src/martian/tests/withtestsmodules/subpackage/__init__.py src/martian/tests/withtestspackages/__init__.py src/martian/tests/withtestspackages/ftests/__init__.py src/martian/tests/withtestspackages/subpackage/__init__.py src/martian/tests/withtestspackages/tests/__init__.py src/martian/tests/withtestspackages/tests/subpackage/__init__.pymartian-0.14/src/martian.egg-info/dependency_links.txt0000644000175000017500000000000111464246720020703 0ustar jwjw martian-0.14/src/martian.egg-info/not-zip-safe0000644000175000017500000000000111464246706017067 0ustar jwjw martian-0.14/src/martian.egg-info/requires.txt0000644000175000017500000000005611464246720017236 0ustar jwjwzope.interface setuptools [test] zope.testingmartian-0.14/src/martian.egg-info/top_level.txt0000644000175000017500000000001011464246720017356 0ustar jwjwmartian martian-0.14/src/martian.egg-info/PKG-INFO0000644000175000017500000010113211464246720015730 0ustar jwjwMetadata-Version: 1.0 Name: martian Version: 0.14 Summary: Martian is a library that allows the embedding of configuration information in Python code. Martian can then grok the system and do the appropriate configuration registrations. One example of a system that uses Martian is the system where it originated: Grok (http://grok.zope.org) Home-page: UNKNOWN Author: Grok project Author-email: grok-dev@zope.org License: ZPL Description: ******* Martian ******* A library to grok configuration from Python code. Martian tutorial **************** Introduction ============ "There was so much to grok, so little to grok from." -- Stranger in a Strange Land, by Robert A. Heinlein Martian provides infrastructure for declarative configuration of Python code. Martian is especially useful for the construction of frameworks that need to provide a flexible plugin infrastructure. Martian doesn't actually provide infrastructure for plugin registries (except for itself). Many frameworks have their own systems for this, and if you need a generic one, you might want to consider ``zope.component``. Martian just allows you to make the registration of plugins less verbose. You can see Martian as doing something that you can also solve with metaclasses, with the following advantages: * the developer of the framework doesn't have to write a lot of ad-hoc metaclasses anymore; instead we offer an infrastructure to make life easier. * configuration doesn't need to happen at import time, but can happen at program startup time. This also makes configuration more tractable for a developer. * we don't bother the developer that *uses* the framework with the surprising behavior that metaclasses sometimes bring. The classes the user has to deal with are normal classes. Why is this package named ``martian``? In the novel "Stranger in a Strange Land", the verb *grok* is introduced: Grok means to understand so thoroughly that the observer becomes a part of the observed -- to merge, blend, intermarry, lose identity in group experience. In the context of this package, "grokking" stands for the process of deducing declarative configuration actions from Python code. In the novel, grokking is originally a concept that comes from the planet Mars. Martians *grok*. Since this package helps you grok code, it's called Martian. Martian provides a framework that allows configuration to be expressed in declarative Python code. These declarations can often be deduced from the structure of the code itself. The idea is to make these declarations so minimal and easy to read that even extensive configuration does not overly burden the programmers working with the code. The ``martian`` package is a spin-off from the `Grok project`_, in the context of which this codebase was first developed. While Grok uses it, the code is completely independent of Grok. .. _`Grok project`: http://grok.zope.org Motivation ========== "Deducing declarative configuration actions from Python code" - that sounds very abstract. What does it actually mean? What is configuration? What is declarative configuration? In order to explain this, we'll first take a look at configuration. Larger frameworks often offer a lot of points where you can modify their behavior: ways to combine its own components with components you provide yourself to build a larger application. A framework offers points where it can be *configured* with plugin code. When you plug some code into a plugin point, it results in the updating of some registry somewhere with the new plugin. When the framework uses a plugin, it will first look it up in the registry. The action of registering some component into a registry can be called *configuration*. Let's look at an example framework that offers a plugin point. We introduce a very simple framework for plugging in different template languages, where each template language uses its own extension. You can then supply the framework with the template body and the template extension and some data, and render the template. Let's look at the framework:: >>> import string >>> class templating(FakeModule): ... ... class InterpolationTemplate(object): ... "Use %(foo)s for dictionary interpolation." ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... return self.text % kw ... ... class TemplateStringTemplate(object): ... "PEP 292 string substitutions." ... def __init__(self, text): ... self.template = string.Template(text) ... def render(self, **kw): ... return self.template.substitute(**kw) ... ... # the registry, we plug in the two templating systems right away ... extension_handlers = { '.txt': InterpolationTemplate, ... '.tmpl': TemplateStringTemplate } ... ... def render(data, extension, **kw): ... """Render the template at filepath with arguments. ... ... data - the data in the file ... extension - the extension of the file ... keyword arguments - variables to interpolate ... ... In a real framework you could pass in the file path instead of ... data and extension, but we don't want to open files in our ... example. ... ... Returns the rendered template ... """ ... template = extension_handlers[extension](data) ... return template.render(**kw) Since normally we cannot create modules in a doctest, we have emulated the ``templating`` Python module using the ``FakeModule`` class. Whenever you see ``FakeModule`` subclasses, imagine you're looking at a module definition in a ``.py`` file. Now that we have defined a module ``templating``, we also need to be able to import it. Fake modules are always placed automatically into the ``martiantest.fake`` namespace so you can import them from there:: >>> from martiantest.fake import templating Now let's try the ``render`` function for the registered template types, to demonstrate that our framework works:: >>> templating.render('Hello %(name)s!', '.txt', name="world") 'Hello world!' >>> templating.render('Hello ${name}!', '.tmpl', name="universe") 'Hello universe!' File extensions that we do not recognize cause a ``KeyError`` to be raised:: >>> templating.render('Hello', '.silly', name="test") Traceback (most recent call last): ... KeyError: '.silly' We now want to plug into this filehandler framework and provide a handler for ``.silly`` files. Since we are writing a plugin, we cannot change the ``templating`` module directly. Let's write an extension module instead:: >>> class sillytemplating(FakeModule): ... class SillyTemplate(object): ... "Replace {key} with dictionary values." ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... text = self.text ... for key, value in kw.items(): ... text = text.replace('{%s}' % key, value) ... return text ... ... templating.extension_handlers['.silly'] = SillyTemplate >>> from martiantest.fake import sillytemplating In the extension module, we manipulate the ``extension_handlers`` dictionary of the ``templating`` module (in normal code we'd need to import it first), and plug in our own function. ``.silly`` handling works now:: >>> templating.render('Hello {name}!', '.silly', name="galaxy") 'Hello galaxy!' Above we plug into our ``extension_handler`` registry using Python code. Using separate code to manually hook components into registries can get rather cumbersome - each time you write a plugin, you also need to remember you need to register it. Doing template registration in Python code also poses a maintenance risk. It is tempting to start doing fancy things in Python code such as conditional configuration, making the configuration state of a program hard to understand. Another problem is that doing configuration at import time can also lead to unwanted side effects during import, as well as ordering problems, where you want to import something that really needs configuration state in another module that is imported later. Finally, it can also make code harder to test, as configuration is loaded always when you import the module, even if in your test perhaps you don't want it to be. Martian provides a framework that allows configuration to be expressed in declarative Python code. Martian is based on the realization that what to configure where can often be deduced from the structure of Python code itself, especially when it can be annotated with additional declarations. The idea is to make it so easy to write and register a plugin so that even extensive configuration does not overly burden the developer. Configuration actions are executed during a separate phase ("grok time"), not at import time, which makes it easier to reason about and easier to test. Configuration the Martian Way ============================= Let's now transform the above ``templating`` module and the ``sillytemplating`` module to use Martian. First we must recognize that every template language is configured to work for a particular extension. With Martian, we annotate the classes themselves with this configuration information. Annotations happen using *directives*, which look like function calls in the class body. Let's create an ``extension`` directive that can take a single string as an argument, the file extension to register the template class for:: >>> import martian >>> class extension(martian.Directive): ... scope = martian.CLASS ... store = martian.ONCE ... default = None We also need a way to easily recognize all template classes. The normal pattern for this in Martian is to use a base class, so let's define a ``Template`` base class:: >>> class Template(object): ... pass We now have enough infrastructure to allow us to change the code to use Martian style base class and annotations:: >>> class templating(FakeModule): ... ... class InterpolationTemplate(Template): ... "Use %(foo)s for dictionary interpolation." ... extension('.txt') ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... return self.text % kw ... ... class TemplateStringTemplate(Template): ... "PEP 292 string substitutions." ... extension('.tmpl') ... def __init__(self, text): ... self.template = string.Template(text) ... def render(self, **kw): ... return self.template.substitute(**kw) ... ... # the registry, empty to start with ... extension_handlers = {} ... ... def render(data, extension, **kw): ... # this hasn't changed ... template = extension_handlers[extension](data) ... return template.render(**kw) >>> from martiantest.fake import templating As you can see, there have been very few changes: * we made the template classes inherit from ``Template``. * we use the ``extension`` directive in the template classes. * we stopped pre-filling the ``extension_handlers`` dictionary. So how do we fill the ``extension_handlers`` dictionary with the right template languages? Now we can use Martian. We define a *grokker* for ``Template`` that registers the template classes in the ``extension_handlers`` registry:: >>> class meta(FakeModule): ... class TemplateGrokker(martian.ClassGrokker): ... martian.component(Template) ... martian.directive(extension) ... def execute(self, class_, extension, **kw): ... templating.extension_handlers[extension] = class_ ... return True >>> from martiantest.fake import meta What does this do? A ``ClassGrokker`` has its ``execute`` method called for subclasses of what's indicated by the ``martian.component`` directive. You can also declare what directives a ``ClassGrokker`` expects on this component by using ``martian.directive()`` (the ``directive`` directive!) one or more times. The ``execute`` method takes the class to be grokked as the first argument, and the values of the directives used will be passed in as additional parameters into the ``execute`` method. The framework can also pass along an arbitrary number of extra keyword arguments during the grokking process, so we need to declare ``**kw`` to make sure we can handle these. All our grokkers will be collected in a special Martian-specific registry:: >>> reg = martian.GrokkerRegistry() We will need to make sure the system is aware of the ``TemplateGrokker`` defined in the ``meta`` module first, so let's register it first. We can do this by simply grokking the ``meta`` module:: >>> reg.grok('meta', meta) True Because ``TemplateGrokker`` is now registered, our registry now knows how to grok ``Template`` subclasses. Let's grok the ``templating`` module:: >>> reg.grok('templating', templating) True Let's try the ``render`` function of templating again, to demonstrate we have successfully grokked the template classes:: >>> templating.render('Hello %(name)s!', '.txt', name="world") 'Hello world!' >>> templating.render('Hello ${name}!', '.tmpl', name="universe") 'Hello universe!' ``.silly`` hasn't been registered yet:: >>> templating.render('Hello', '.silly', name="test") Traceback (most recent call last): ... KeyError: '.silly' Let's now register ``.silly`` from an extension module:: >>> class sillytemplating(FakeModule): ... class SillyTemplate(Template): ... "Replace {key} with dictionary values." ... extension('.silly') ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... text = self.text ... for key, value in kw.items(): ... text = text.replace('{%s}' % key, value) ... return text >>> from martiantest.fake import sillytemplating As you can see, the developer that uses the framework has no need anymore to know about ``templating.extension_handlers``. Instead we can simply grok the module to have ``SillyTemplate`` be register appropriately:: >>> reg.grok('sillytemplating', sillytemplating) True We can now use the ``.silly`` templating engine too:: >>> templating.render('Hello {name}!', '.silly', name="galaxy") 'Hello galaxy!' Admittedly it is hard to demonstrate Martian well with a small example like this. In the end we have actually written more code than in the basic framework, after all. But even in this small example, the ``templating`` and ``sillytemplating`` module have become more declarative in nature. The developer that uses the framework will not need to know anymore about things like ``templating.extension_handlers`` or an API to register things there. Instead the developer can registering a new template system anywhere, as long as he subclasses from ``Template``, and as long as his code is grokked by the system. Finally note how Martian was used to define the ``TemplateGrokker`` as well. In this way Martian can use itself to extend itself. Grokking instances ================== Above we've seen how you can grok classes. Martian also supplies a way to grok instances. This is less common in typical frameworks, and has the drawback that no class-level directives can be used, but can still be useful. Let's imagine a case where we have a zoo framework with an ``Animal`` class, and we want to track instances of it:: >>> class Animal(object): ... def __init__(self, name): ... self.name = name >>> class zoo(FakeModule): ... horse = Animal('horse') ... chicken = Animal('chicken') ... elephant = Animal('elephant') ... lion = Animal('lion') ... animals = {} >>> from martiantest.fake import zoo We define an ``InstanceGrokker`` subclass to grok ``Animal`` instances:: >>> class meta(FakeModule): ... class AnimalGrokker(martian.InstanceGrokker): ... martian.component(Animal) ... def execute(self, instance, **kw): ... zoo.animals[instance.name] = instance ... return True >>> from martiantest.fake import meta Let's create a new registry with the ``AnimalGrokker`` in it:: >>> reg = martian.GrokkerRegistry() >>> reg.grok('meta', meta) True We can now grok the ``zoo`` module:: >>> reg.grok('zoo', zoo) True The animals will now be in the ``animals`` dictionary:: >>> sorted(zoo.animals.items()) [('chicken', ), ('elephant', ), ('horse', ), ('lion', )] More information ================ For many more details and examples of more kinds of grokkers, please see ``src/martian/core.txt``. For more information on directives see ``src/martian/directive.txt``. CHANGES ******* 0.14 (2010-11-03) ================= Feature changes --------------- * The computation of the default value for a directive can now be defined inside the directive class definition. Whenever there is a ``get_default`` classmethod, it is used for computing the default:: class name(Directive): scope = CLASS store = ONCE @classmethod def get_default(cls, component, module=None, **data): return component.__name__.lower() When binding the directive, the default-default behaviour can still be overriden by passing a ``get_default`` function:: def another_default(component, module=None, **data): return component.__name__.lower() name.bind(get_default=another_default).get(some_component) Making the default behaviour intrinsic to the directive, prevents having to pass the ``get_default`` function over and over when getting values, for example in the grokkers. 0.13 (2010-11-01) ================= Feature changes --------------- * Ignore all __main__ modules. * List zope.testing as a test dependency. 0.12 (2009-06-29) ================= Feature changes --------------- * Changes to better support various inheritance scenarios in combination with directives. Details follow. * ``CLASS_OR_MODULE`` scope directives will be aware of inheritance of values that are defined in module-scope. Consider the following case:: module a: some_directive('A') class Foo(object): pass module b: import a class Bar(a.Foo): pass As before, ``Foo`` will have the value ``A`` configured for it. ``Bar``, since it inherits from ``Foo``, will inherit this value. * ``CLASS_OR_MODULE`` and ``CLASS`` scope directives will be aware of inheritance of computed default values. Consider the following case:: module a: class Foo(object): pass module b: import a class Bar(a.Foo): pass def get_default(component, module, **data): if module.__name__ == 'a': return "we have a default value for module a" return martian.UNKNOWN When we now do this:: some_directive.bind(get_default=get_default).get(b.Bar) We will get the value "we have a default value for module a". This is because when trying to compute the default value for ``Bar`` we returned ``martian.UNKNOWN`` to indicate the value couldn't be found yet. The system then looks at the base class and tries again, and in this case it succeeds (as the module-name is ``a``). * ``martian.ONCE_IFACE`` storage option to allow the creation of directives that store their value on ``zope.interface`` interfaces. This was originally in ``grokcore.view`` but was of wider usefulness. Bugs fixed ---------- * Ignore things that look like Python modules and packages but aren't. These are sometimes created by editors, operating systems and network file systems and we don't want to confuse them. * Ignore .pyc and .pyo files that don't have a matching .py file via ``module_info_from_dotted_name`` if its ``ignore_nonsource`` parameter is ``True``. The default is ``True``. To revert to the older behavior where .pyc files were honored, pass ``ignore_nonsource=False``. * Pass along ``exclude_filter`` (and the new ``ignore_nonsource`` flag) to ModuleInfo constructor when it calls itself recursively. * Replace ``fake_import`` to import fake modules in tests with a real python import statement (``from martiantest.fake import my_fake_module``). This works by introducing a metaclass for ``FakeModule`` that automatically registers it as a module. The irony does not escape us. This also means that ``martian.scan.resolve()`` will now work on fake modules. 0.11 (2008-09-24) ================= Feature changes --------------- * Added MULTIPLE_NOBASE option for directive store. This is like MULTIPLE but doesn't inherit information from the base class. 0.10 (2008-06-06) ================= Feature changes --------------- * Add a ``validateClass`` validate function for directives. * Moved ``FakeModule`` and ``fake_import`` into a ``martian.testing`` module so that they can be reused by external packages. * Introduce new tutorial text as README.txt. The text previously in ``README.txt`` was rather too detailed for a tutorial, so has been moved into ``core.txt``. * Introduce a ``GrokkerRegistry`` class that is a ``ModuleGrokker`` with a ``MetaMultiGrokker`` in it. This is the convenient thing to instantiate to start working with Grok and is demonstrated in the tutorial. * Introduced three new martian-specific directives: ``martian.component``, ``martian.directive`` and ``martian.priority``. These replace the ``component_class``, ``directives`` and ``priority`` class-level attributes. This way Grokkers look the same as what they grok. This breaks backwards compatibility again, but it's an easy replace operation. Note that ``martian.directive`` takes the directive itself as an argument, and then optionally the same arguments as the ``bind`` method of directives (``name``, ``default`` and ``get_default``). It may be used multiple times. Note that ``martian.baseclass`` was already a Martian-specific directive and this has been unchanged. * For symmetry, add an ``execute`` method to ``InstanceGrokker``. 0.9.7 (2008-05-29) ================== Feature changes --------------- * Added a ``MethodGrokker`` base class for grokkers that want to grok methods of a class rather than the whole class itself. It works quite similar to the ``ClassGrokker`` regarding directive definition, except that directives evaluated not only on class (and possibly module) level but also for each method. That way, directives can also be applied to methods (as decorators) in case they support it. 0.9.6 (2008-05-14) ================== Feature changes --------------- * Refactored the ``martian.Directive`` base class yet again to allow more declarative (rather than imperative) usage in grokkers. Directives themselves no longer have a ``get()`` method nor a default value factory (``get_default()``). Instead you will have to "bind" the directive first which is typically done in a grokker. * Extended the ``ClassGrokker`` baseclass with a standard ``grok()`` method that allows you to simply declare a set of directives that are used on the grokked classes. Then you just have to implement an ``execute()`` method that will receive the data from those directives as keyword arguments. This simplifies the implementation of class grokkers a lot. 0.9.5 (2008-05-04) ================== * ``scan_for_classes`` just needs a single second argument specifying an interface. The support for scanning for subclasses directly has been removed as it became unnecessary (due to changes in grokcore.component). 0.9.4 (2008-05-04) ================== Features changes ---------------- * Replaced the various directive base classes with a single ``martian.Directive`` base class: - The directive scope is now defined with the ``scope`` class attribute using one of ``martian.CLASS``, ``martian.MODULE``, ``martian.CLASS_OR_MODULE``. - The type of storage is defined with the ``store`` class attribute using one of ``martian.ONCE``, ``martian.MULTIPLE``, ``martian.DICT``. - Directives have now gained the ability to read the value that they have set on a component or module using a ``get()`` method. The ``class_annotation`` and ``class_annotation_list`` helpers have been removed as a consequence. * Moved the ``baseclass()`` directive from Grok to Martian. * Added a ``martian.util.check_provides_one`` helper, in analogy to ``check_implements_one``. * The ``scan_for_classes`` helper now also accepts an ``interface`` argument which allows you to scan for classes based on interface rather than base classes. Bug fixes --------- * added dummy ``package_dotted_name`` to ``BuiltinModuleInfo``. This allows the grokking of views in test code using Grok's ``grok.testing.grok_component`` without a failure when it sets up the ``static`` attribute. * no longer use the convention that classes ending in -Base will be considered base classes. You must now explicitly use the grok.baseclass() directive. * The type check of classes uses isinstance() instead of type(). This means Grok can work with Zope 2 ExtensionClasses and metaclass programming. 0.9.3 (2008-01-26) ================== Feature changes --------------- * Added an OptionalValueDirective which allows the construction of directives that take either zero or one argument. If no arguments are given, the ``default_value`` method on the directive is called. Subclasses need to override this to return the default value to use. Restructuring ------------- * Move some util functions that were really grok-specific out of Martian back into Grok. 0.9.2 (2007-11-20) ================== Bug fixes --------- * scan.module_info_from_dotted_name() now has special behavior when it runs into __builtin__. Previously, it would crash with an error. Now it will return an instance of BuiltinModuleInfo. This is a very simple implementation which provides just enough information to make client code work. Typically this client code is test-related so that the module context will be __builtin__. 0.9.1 (2007-10-30) ================== Feature changes --------------- * Grokkers now receive a ``module_info`` keyword argument. This change is completely backwards-compatible since grokkers which don't take ``module_info`` explicitly will absorb the extra argument in ``**kw``. 0.9 (2007-10-02) ================= Feature changes --------------- * Reverted the behaviour where modules called tests or ftests were skipped by default and added an API to provides a filtering function for skipping modules to be grokked. 0.8.1 (2007-08-13) ================== Feature changes --------------- * Don't grok tests or ftests modules. Bugs fixed ---------- * Fix a bug where if a class had multiple base classes, this could end up in the resultant list multiple times. 0.8 (2007-07-02) ================ Feature changes --------------- * Initial public release. Download ******** Platform: UNKNOWN martian-0.14/CREDITS.txt0000644000175000017500000000033211464246706012561 0ustar jwjwCREDITS ======= * Martijn Faassen * Wolfgang Schnerring * Christian Theune * Philipp von Weitershausen * Jan-Wijbrand Kolman * Luciano Ramalho * Chris McDonough Thank you --------- * The core Grok developers. martian-0.14/PKG-INFO0000644000175000017500000010113211464246723012017 0ustar jwjwMetadata-Version: 1.0 Name: martian Version: 0.14 Summary: Martian is a library that allows the embedding of configuration information in Python code. Martian can then grok the system and do the appropriate configuration registrations. One example of a system that uses Martian is the system where it originated: Grok (http://grok.zope.org) Home-page: UNKNOWN Author: Grok project Author-email: grok-dev@zope.org License: ZPL Description: ******* Martian ******* A library to grok configuration from Python code. Martian tutorial **************** Introduction ============ "There was so much to grok, so little to grok from." -- Stranger in a Strange Land, by Robert A. Heinlein Martian provides infrastructure for declarative configuration of Python code. Martian is especially useful for the construction of frameworks that need to provide a flexible plugin infrastructure. Martian doesn't actually provide infrastructure for plugin registries (except for itself). Many frameworks have their own systems for this, and if you need a generic one, you might want to consider ``zope.component``. Martian just allows you to make the registration of plugins less verbose. You can see Martian as doing something that you can also solve with metaclasses, with the following advantages: * the developer of the framework doesn't have to write a lot of ad-hoc metaclasses anymore; instead we offer an infrastructure to make life easier. * configuration doesn't need to happen at import time, but can happen at program startup time. This also makes configuration more tractable for a developer. * we don't bother the developer that *uses* the framework with the surprising behavior that metaclasses sometimes bring. The classes the user has to deal with are normal classes. Why is this package named ``martian``? In the novel "Stranger in a Strange Land", the verb *grok* is introduced: Grok means to understand so thoroughly that the observer becomes a part of the observed -- to merge, blend, intermarry, lose identity in group experience. In the context of this package, "grokking" stands for the process of deducing declarative configuration actions from Python code. In the novel, grokking is originally a concept that comes from the planet Mars. Martians *grok*. Since this package helps you grok code, it's called Martian. Martian provides a framework that allows configuration to be expressed in declarative Python code. These declarations can often be deduced from the structure of the code itself. The idea is to make these declarations so minimal and easy to read that even extensive configuration does not overly burden the programmers working with the code. The ``martian`` package is a spin-off from the `Grok project`_, in the context of which this codebase was first developed. While Grok uses it, the code is completely independent of Grok. .. _`Grok project`: http://grok.zope.org Motivation ========== "Deducing declarative configuration actions from Python code" - that sounds very abstract. What does it actually mean? What is configuration? What is declarative configuration? In order to explain this, we'll first take a look at configuration. Larger frameworks often offer a lot of points where you can modify their behavior: ways to combine its own components with components you provide yourself to build a larger application. A framework offers points where it can be *configured* with plugin code. When you plug some code into a plugin point, it results in the updating of some registry somewhere with the new plugin. When the framework uses a plugin, it will first look it up in the registry. The action of registering some component into a registry can be called *configuration*. Let's look at an example framework that offers a plugin point. We introduce a very simple framework for plugging in different template languages, where each template language uses its own extension. You can then supply the framework with the template body and the template extension and some data, and render the template. Let's look at the framework:: >>> import string >>> class templating(FakeModule): ... ... class InterpolationTemplate(object): ... "Use %(foo)s for dictionary interpolation." ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... return self.text % kw ... ... class TemplateStringTemplate(object): ... "PEP 292 string substitutions." ... def __init__(self, text): ... self.template = string.Template(text) ... def render(self, **kw): ... return self.template.substitute(**kw) ... ... # the registry, we plug in the two templating systems right away ... extension_handlers = { '.txt': InterpolationTemplate, ... '.tmpl': TemplateStringTemplate } ... ... def render(data, extension, **kw): ... """Render the template at filepath with arguments. ... ... data - the data in the file ... extension - the extension of the file ... keyword arguments - variables to interpolate ... ... In a real framework you could pass in the file path instead of ... data and extension, but we don't want to open files in our ... example. ... ... Returns the rendered template ... """ ... template = extension_handlers[extension](data) ... return template.render(**kw) Since normally we cannot create modules in a doctest, we have emulated the ``templating`` Python module using the ``FakeModule`` class. Whenever you see ``FakeModule`` subclasses, imagine you're looking at a module definition in a ``.py`` file. Now that we have defined a module ``templating``, we also need to be able to import it. Fake modules are always placed automatically into the ``martiantest.fake`` namespace so you can import them from there:: >>> from martiantest.fake import templating Now let's try the ``render`` function for the registered template types, to demonstrate that our framework works:: >>> templating.render('Hello %(name)s!', '.txt', name="world") 'Hello world!' >>> templating.render('Hello ${name}!', '.tmpl', name="universe") 'Hello universe!' File extensions that we do not recognize cause a ``KeyError`` to be raised:: >>> templating.render('Hello', '.silly', name="test") Traceback (most recent call last): ... KeyError: '.silly' We now want to plug into this filehandler framework and provide a handler for ``.silly`` files. Since we are writing a plugin, we cannot change the ``templating`` module directly. Let's write an extension module instead:: >>> class sillytemplating(FakeModule): ... class SillyTemplate(object): ... "Replace {key} with dictionary values." ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... text = self.text ... for key, value in kw.items(): ... text = text.replace('{%s}' % key, value) ... return text ... ... templating.extension_handlers['.silly'] = SillyTemplate >>> from martiantest.fake import sillytemplating In the extension module, we manipulate the ``extension_handlers`` dictionary of the ``templating`` module (in normal code we'd need to import it first), and plug in our own function. ``.silly`` handling works now:: >>> templating.render('Hello {name}!', '.silly', name="galaxy") 'Hello galaxy!' Above we plug into our ``extension_handler`` registry using Python code. Using separate code to manually hook components into registries can get rather cumbersome - each time you write a plugin, you also need to remember you need to register it. Doing template registration in Python code also poses a maintenance risk. It is tempting to start doing fancy things in Python code such as conditional configuration, making the configuration state of a program hard to understand. Another problem is that doing configuration at import time can also lead to unwanted side effects during import, as well as ordering problems, where you want to import something that really needs configuration state in another module that is imported later. Finally, it can also make code harder to test, as configuration is loaded always when you import the module, even if in your test perhaps you don't want it to be. Martian provides a framework that allows configuration to be expressed in declarative Python code. Martian is based on the realization that what to configure where can often be deduced from the structure of Python code itself, especially when it can be annotated with additional declarations. The idea is to make it so easy to write and register a plugin so that even extensive configuration does not overly burden the developer. Configuration actions are executed during a separate phase ("grok time"), not at import time, which makes it easier to reason about and easier to test. Configuration the Martian Way ============================= Let's now transform the above ``templating`` module and the ``sillytemplating`` module to use Martian. First we must recognize that every template language is configured to work for a particular extension. With Martian, we annotate the classes themselves with this configuration information. Annotations happen using *directives*, which look like function calls in the class body. Let's create an ``extension`` directive that can take a single string as an argument, the file extension to register the template class for:: >>> import martian >>> class extension(martian.Directive): ... scope = martian.CLASS ... store = martian.ONCE ... default = None We also need a way to easily recognize all template classes. The normal pattern for this in Martian is to use a base class, so let's define a ``Template`` base class:: >>> class Template(object): ... pass We now have enough infrastructure to allow us to change the code to use Martian style base class and annotations:: >>> class templating(FakeModule): ... ... class InterpolationTemplate(Template): ... "Use %(foo)s for dictionary interpolation." ... extension('.txt') ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... return self.text % kw ... ... class TemplateStringTemplate(Template): ... "PEP 292 string substitutions." ... extension('.tmpl') ... def __init__(self, text): ... self.template = string.Template(text) ... def render(self, **kw): ... return self.template.substitute(**kw) ... ... # the registry, empty to start with ... extension_handlers = {} ... ... def render(data, extension, **kw): ... # this hasn't changed ... template = extension_handlers[extension](data) ... return template.render(**kw) >>> from martiantest.fake import templating As you can see, there have been very few changes: * we made the template classes inherit from ``Template``. * we use the ``extension`` directive in the template classes. * we stopped pre-filling the ``extension_handlers`` dictionary. So how do we fill the ``extension_handlers`` dictionary with the right template languages? Now we can use Martian. We define a *grokker* for ``Template`` that registers the template classes in the ``extension_handlers`` registry:: >>> class meta(FakeModule): ... class TemplateGrokker(martian.ClassGrokker): ... martian.component(Template) ... martian.directive(extension) ... def execute(self, class_, extension, **kw): ... templating.extension_handlers[extension] = class_ ... return True >>> from martiantest.fake import meta What does this do? A ``ClassGrokker`` has its ``execute`` method called for subclasses of what's indicated by the ``martian.component`` directive. You can also declare what directives a ``ClassGrokker`` expects on this component by using ``martian.directive()`` (the ``directive`` directive!) one or more times. The ``execute`` method takes the class to be grokked as the first argument, and the values of the directives used will be passed in as additional parameters into the ``execute`` method. The framework can also pass along an arbitrary number of extra keyword arguments during the grokking process, so we need to declare ``**kw`` to make sure we can handle these. All our grokkers will be collected in a special Martian-specific registry:: >>> reg = martian.GrokkerRegistry() We will need to make sure the system is aware of the ``TemplateGrokker`` defined in the ``meta`` module first, so let's register it first. We can do this by simply grokking the ``meta`` module:: >>> reg.grok('meta', meta) True Because ``TemplateGrokker`` is now registered, our registry now knows how to grok ``Template`` subclasses. Let's grok the ``templating`` module:: >>> reg.grok('templating', templating) True Let's try the ``render`` function of templating again, to demonstrate we have successfully grokked the template classes:: >>> templating.render('Hello %(name)s!', '.txt', name="world") 'Hello world!' >>> templating.render('Hello ${name}!', '.tmpl', name="universe") 'Hello universe!' ``.silly`` hasn't been registered yet:: >>> templating.render('Hello', '.silly', name="test") Traceback (most recent call last): ... KeyError: '.silly' Let's now register ``.silly`` from an extension module:: >>> class sillytemplating(FakeModule): ... class SillyTemplate(Template): ... "Replace {key} with dictionary values." ... extension('.silly') ... def __init__(self, text): ... self.text = text ... def render(self, **kw): ... text = self.text ... for key, value in kw.items(): ... text = text.replace('{%s}' % key, value) ... return text >>> from martiantest.fake import sillytemplating As you can see, the developer that uses the framework has no need anymore to know about ``templating.extension_handlers``. Instead we can simply grok the module to have ``SillyTemplate`` be register appropriately:: >>> reg.grok('sillytemplating', sillytemplating) True We can now use the ``.silly`` templating engine too:: >>> templating.render('Hello {name}!', '.silly', name="galaxy") 'Hello galaxy!' Admittedly it is hard to demonstrate Martian well with a small example like this. In the end we have actually written more code than in the basic framework, after all. But even in this small example, the ``templating`` and ``sillytemplating`` module have become more declarative in nature. The developer that uses the framework will not need to know anymore about things like ``templating.extension_handlers`` or an API to register things there. Instead the developer can registering a new template system anywhere, as long as he subclasses from ``Template``, and as long as his code is grokked by the system. Finally note how Martian was used to define the ``TemplateGrokker`` as well. In this way Martian can use itself to extend itself. Grokking instances ================== Above we've seen how you can grok classes. Martian also supplies a way to grok instances. This is less common in typical frameworks, and has the drawback that no class-level directives can be used, but can still be useful. Let's imagine a case where we have a zoo framework with an ``Animal`` class, and we want to track instances of it:: >>> class Animal(object): ... def __init__(self, name): ... self.name = name >>> class zoo(FakeModule): ... horse = Animal('horse') ... chicken = Animal('chicken') ... elephant = Animal('elephant') ... lion = Animal('lion') ... animals = {} >>> from martiantest.fake import zoo We define an ``InstanceGrokker`` subclass to grok ``Animal`` instances:: >>> class meta(FakeModule): ... class AnimalGrokker(martian.InstanceGrokker): ... martian.component(Animal) ... def execute(self, instance, **kw): ... zoo.animals[instance.name] = instance ... return True >>> from martiantest.fake import meta Let's create a new registry with the ``AnimalGrokker`` in it:: >>> reg = martian.GrokkerRegistry() >>> reg.grok('meta', meta) True We can now grok the ``zoo`` module:: >>> reg.grok('zoo', zoo) True The animals will now be in the ``animals`` dictionary:: >>> sorted(zoo.animals.items()) [('chicken', ), ('elephant', ), ('horse', ), ('lion', )] More information ================ For many more details and examples of more kinds of grokkers, please see ``src/martian/core.txt``. For more information on directives see ``src/martian/directive.txt``. CHANGES ******* 0.14 (2010-11-03) ================= Feature changes --------------- * The computation of the default value for a directive can now be defined inside the directive class definition. Whenever there is a ``get_default`` classmethod, it is used for computing the default:: class name(Directive): scope = CLASS store = ONCE @classmethod def get_default(cls, component, module=None, **data): return component.__name__.lower() When binding the directive, the default-default behaviour can still be overriden by passing a ``get_default`` function:: def another_default(component, module=None, **data): return component.__name__.lower() name.bind(get_default=another_default).get(some_component) Making the default behaviour intrinsic to the directive, prevents having to pass the ``get_default`` function over and over when getting values, for example in the grokkers. 0.13 (2010-11-01) ================= Feature changes --------------- * Ignore all __main__ modules. * List zope.testing as a test dependency. 0.12 (2009-06-29) ================= Feature changes --------------- * Changes to better support various inheritance scenarios in combination with directives. Details follow. * ``CLASS_OR_MODULE`` scope directives will be aware of inheritance of values that are defined in module-scope. Consider the following case:: module a: some_directive('A') class Foo(object): pass module b: import a class Bar(a.Foo): pass As before, ``Foo`` will have the value ``A`` configured for it. ``Bar``, since it inherits from ``Foo``, will inherit this value. * ``CLASS_OR_MODULE`` and ``CLASS`` scope directives will be aware of inheritance of computed default values. Consider the following case:: module a: class Foo(object): pass module b: import a class Bar(a.Foo): pass def get_default(component, module, **data): if module.__name__ == 'a': return "we have a default value for module a" return martian.UNKNOWN When we now do this:: some_directive.bind(get_default=get_default).get(b.Bar) We will get the value "we have a default value for module a". This is because when trying to compute the default value for ``Bar`` we returned ``martian.UNKNOWN`` to indicate the value couldn't be found yet. The system then looks at the base class and tries again, and in this case it succeeds (as the module-name is ``a``). * ``martian.ONCE_IFACE`` storage option to allow the creation of directives that store their value on ``zope.interface`` interfaces. This was originally in ``grokcore.view`` but was of wider usefulness. Bugs fixed ---------- * Ignore things that look like Python modules and packages but aren't. These are sometimes created by editors, operating systems and network file systems and we don't want to confuse them. * Ignore .pyc and .pyo files that don't have a matching .py file via ``module_info_from_dotted_name`` if its ``ignore_nonsource`` parameter is ``True``. The default is ``True``. To revert to the older behavior where .pyc files were honored, pass ``ignore_nonsource=False``. * Pass along ``exclude_filter`` (and the new ``ignore_nonsource`` flag) to ModuleInfo constructor when it calls itself recursively. * Replace ``fake_import`` to import fake modules in tests with a real python import statement (``from martiantest.fake import my_fake_module``). This works by introducing a metaclass for ``FakeModule`` that automatically registers it as a module. The irony does not escape us. This also means that ``martian.scan.resolve()`` will now work on fake modules. 0.11 (2008-09-24) ================= Feature changes --------------- * Added MULTIPLE_NOBASE option for directive store. This is like MULTIPLE but doesn't inherit information from the base class. 0.10 (2008-06-06) ================= Feature changes --------------- * Add a ``validateClass`` validate function for directives. * Moved ``FakeModule`` and ``fake_import`` into a ``martian.testing`` module so that they can be reused by external packages. * Introduce new tutorial text as README.txt. The text previously in ``README.txt`` was rather too detailed for a tutorial, so has been moved into ``core.txt``. * Introduce a ``GrokkerRegistry`` class that is a ``ModuleGrokker`` with a ``MetaMultiGrokker`` in it. This is the convenient thing to instantiate to start working with Grok and is demonstrated in the tutorial. * Introduced three new martian-specific directives: ``martian.component``, ``martian.directive`` and ``martian.priority``. These replace the ``component_class``, ``directives`` and ``priority`` class-level attributes. This way Grokkers look the same as what they grok. This breaks backwards compatibility again, but it's an easy replace operation. Note that ``martian.directive`` takes the directive itself as an argument, and then optionally the same arguments as the ``bind`` method of directives (``name``, ``default`` and ``get_default``). It may be used multiple times. Note that ``martian.baseclass`` was already a Martian-specific directive and this has been unchanged. * For symmetry, add an ``execute`` method to ``InstanceGrokker``. 0.9.7 (2008-05-29) ================== Feature changes --------------- * Added a ``MethodGrokker`` base class for grokkers that want to grok methods of a class rather than the whole class itself. It works quite similar to the ``ClassGrokker`` regarding directive definition, except that directives evaluated not only on class (and possibly module) level but also for each method. That way, directives can also be applied to methods (as decorators) in case they support it. 0.9.6 (2008-05-14) ================== Feature changes --------------- * Refactored the ``martian.Directive`` base class yet again to allow more declarative (rather than imperative) usage in grokkers. Directives themselves no longer have a ``get()`` method nor a default value factory (``get_default()``). Instead you will have to "bind" the directive first which is typically done in a grokker. * Extended the ``ClassGrokker`` baseclass with a standard ``grok()`` method that allows you to simply declare a set of directives that are used on the grokked classes. Then you just have to implement an ``execute()`` method that will receive the data from those directives as keyword arguments. This simplifies the implementation of class grokkers a lot. 0.9.5 (2008-05-04) ================== * ``scan_for_classes`` just needs a single second argument specifying an interface. The support for scanning for subclasses directly has been removed as it became unnecessary (due to changes in grokcore.component). 0.9.4 (2008-05-04) ================== Features changes ---------------- * Replaced the various directive base classes with a single ``martian.Directive`` base class: - The directive scope is now defined with the ``scope`` class attribute using one of ``martian.CLASS``, ``martian.MODULE``, ``martian.CLASS_OR_MODULE``. - The type of storage is defined with the ``store`` class attribute using one of ``martian.ONCE``, ``martian.MULTIPLE``, ``martian.DICT``. - Directives have now gained the ability to read the value that they have set on a component or module using a ``get()`` method. The ``class_annotation`` and ``class_annotation_list`` helpers have been removed as a consequence. * Moved the ``baseclass()`` directive from Grok to Martian. * Added a ``martian.util.check_provides_one`` helper, in analogy to ``check_implements_one``. * The ``scan_for_classes`` helper now also accepts an ``interface`` argument which allows you to scan for classes based on interface rather than base classes. Bug fixes --------- * added dummy ``package_dotted_name`` to ``BuiltinModuleInfo``. This allows the grokking of views in test code using Grok's ``grok.testing.grok_component`` without a failure when it sets up the ``static`` attribute. * no longer use the convention that classes ending in -Base will be considered base classes. You must now explicitly use the grok.baseclass() directive. * The type check of classes uses isinstance() instead of type(). This means Grok can work with Zope 2 ExtensionClasses and metaclass programming. 0.9.3 (2008-01-26) ================== Feature changes --------------- * Added an OptionalValueDirective which allows the construction of directives that take either zero or one argument. If no arguments are given, the ``default_value`` method on the directive is called. Subclasses need to override this to return the default value to use. Restructuring ------------- * Move some util functions that were really grok-specific out of Martian back into Grok. 0.9.2 (2007-11-20) ================== Bug fixes --------- * scan.module_info_from_dotted_name() now has special behavior when it runs into __builtin__. Previously, it would crash with an error. Now it will return an instance of BuiltinModuleInfo. This is a very simple implementation which provides just enough information to make client code work. Typically this client code is test-related so that the module context will be __builtin__. 0.9.1 (2007-10-30) ================== Feature changes --------------- * Grokkers now receive a ``module_info`` keyword argument. This change is completely backwards-compatible since grokkers which don't take ``module_info`` explicitly will absorb the extra argument in ``**kw``. 0.9 (2007-10-02) ================= Feature changes --------------- * Reverted the behaviour where modules called tests or ftests were skipped by default and added an API to provides a filtering function for skipping modules to be grokked. 0.8.1 (2007-08-13) ================== Feature changes --------------- * Don't grok tests or ftests modules. Bugs fixed ---------- * Fix a bug where if a class had multiple base classes, this could end up in the resultant list multiple times. 0.8 (2007-07-02) ================ Feature changes --------------- * Initial public release. Download ******** Platform: UNKNOWN martian-0.14/COPYRIGHT.txt0000644000175000017500000000004011464246706013030 0ustar jwjwZope Foundation and Contributorsmartian-0.14/buildout.cfg0000644000175000017500000000030311464246706013231 0ustar jwjw[buildout] develop = . parts = devpython test [devpython] recipe = zc.recipe.egg interpreter = devpython eggs = martian [test] recipe = zc.recipe.testrunner eggs = martian martian[test]