diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eee466f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.installed.cfg +bin/ +develop-eggs/ +eggs/ + +*.py[co] +__pycache__/ +build/ +dist/ +*.egg-info/ +.tox/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c0929a4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: python +python: + - 2.6 + - 2.7 +install: + - python bootstrap.py + - bin/buildout +script: + - bin/test -v1 +notifications: + email: false diff --git a/CHANGES.txt b/CHANGES.txt index 6439fb6..7aa4b27 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,14 @@ Changes ======= +2.5.1 (unreleased) +------------------ + +- Add ability to exclude more than one module or package using + ```` and allow to use unix shell-style + wildcards within. (Backport of this feature from version 2.7) + + 2.5 (2012-05-01) ---------------- diff --git a/README.txt b/README.txt index 38d1a87..772c5f1 100644 --- a/README.txt +++ b/README.txt @@ -49,6 +49,10 @@ To sum up, your ``site.zcml`` file should look like something like this:: +There is an optional ``exclude`` on the `grok` directive. It allows to specify +names of packages or modules that if encountered won't be grokked. These +names might contain unix shell-style wildcards. + Examples ======== diff --git a/bootstrap.py b/bootstrap.py index a3d5b92..1f59b21 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -18,73 +18,17 @@ use the -c option to specify an alternate configuration file. """ -import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess +import os +import shutil +import sys +import tempfile + 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) +__version__ = '2015-07-01' +# See zc.buildout's changelog if this version is up to date. + +tmpeggs = tempfile.mkdtemp(prefix='bootstrap-') usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] @@ -94,128 +38,134 @@ def normalize_to_url(option, opt_str, value, parser): 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. +Note that by using --find-links 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("--version", + action="store_true", default=False, + help=("Return bootstrap.py version.")) 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 " + help=("Normally, if you do not specify a --buildout-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.")) +parser.add_option("-c", "--config-file", + help=("Specify the path to the buildout configuration " + "file to be used.")) +parser.add_option("-f", "--find-links", + help=("Specify a URL to search for buildout releases")) +parser.add_option("--allow-site-packages", + action="store_true", default=False, + help=("Let bootstrap.py use existing site packages")) +parser.add_option("--buildout-version", + help="Use a specific zc.buildout version") +parser.add_option("--setuptools-version", + help="Use a specific setuptools version") +parser.add_option("--setuptools-to-dir", + help=("Allow for re-use of existing directory of " + "setuptools versions")) options, args = parser.parse_args() +if options.version: + print("bootstrap.py version %s" % __version__) + sys.exit(0) -# 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') +###################################################################### +# load/install setuptools try: - import pkg_resources - import setuptools # A flag. Sometimes pkg_resources is installed alone. - if not hasattr(pkg_resources, '_distribute'): - raise ImportError + from urllib.request import urlopen 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)]) + from urllib2 import urlopen -if options.use_distribute: - setup_requirement = 'distribute' +ez = {} +if os.path.exists('ez_setup.py'): + exec(open('ez_setup.py').read(), ez) else: - setup_requirement = 'setuptools' + exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) + +if not options.allow_site_packages: + # ez_setup imports site, which adds site packages + # this will remove them from the path to ensure that incompatible versions + # of setuptools are not in the path + import site + # inside a virtualenv, there is no 'getsitepackages'. + # We can't remove these reliably + if hasattr(site, 'getsitepackages'): + for sitepackage_path in site.getsitepackages(): + # Strip all site-packages directories from sys.path that + # are not sys.prefix; this is because on Windows + # sys.prefix is a site-package directory. + if sitepackage_path != sys.prefix: + sys.path[:] = [x for x in sys.path + if sitepackage_path not in x] + +setup_args = dict(to_dir=tmpeggs, download_delay=0) + +if options.setuptools_version is not None: + setup_args['version'] = options.setuptools_version +if options.setuptools_to_dir is not None: + setup_args['to_dir'] = options.setuptools_to_dir + +ez['use_setuptools'](**setup_args) +import setuptools +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) + +###################################################################### +# Install buildout + 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) + +setuptools_path = ws.find( + pkg_resources.Requirement.parse('setuptools')).location + +# Fix sys.path here as easy_install.pth added before PYTHONPATH +cmd = [sys.executable, '-c', + 'import sys; sys.path[0:0] = [%r]; ' % setuptools_path + + 'from setuptools.command.easy_install import main; main()', + '-mZqNxd', tmpeggs] + +find_links = os.environ.get( + 'bootstrap-testing-find-links', + options.find_links or + ('http://downloads.buildout.org/' + if options.accept_buildout_test_releases else None) + ) +if find_links: + cmd.extend(['-f', find_links]) requirement = 'zc.buildout' -version = options.version +version = options.buildout_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 + try: + return not parsed_version.is_prerelease + except AttributeError: + # Older setuptools + 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]) + search_path=[setuptools_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) @@ -237,22 +187,24 @@ def _final_version(parsed_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) +import subprocess +if subprocess.call(cmd) != 0: + raise Exception( + "Failed to execute command:\n%s" % repr(cmd)[1:-1]) + +###################################################################### +# Import and run buildout + +ws.add_entry(tmpeggs) ws.require(requirement) import zc.buildout.buildout + +if not [a for a in args if '=' not in a]: + args.append('bootstrap') + +# if -c was provided, we push it back into args for buildout' main function +if options.config_file is not None: + args[0:0] = ['-c', options.config_file] + zc.buildout.buildout.main(args) -if not options.eggs: # clean up temporary egg directory - shutil.rmtree(eggs_dir) +shutil.rmtree(tmpeggs) diff --git a/buildout.cfg b/buildout.cfg index b264c74..b7c5fe3 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -1,9 +1,8 @@ [buildout] develop = . parts = interpreter test -extends = http://svn.zope.org/repos/main/groktoolkit/trunk/grok.cfg +extends = https://raw.githubusercontent.com/zopefoundation/groktoolkit/master/grok.cfg versions = versions -extensions = buildout.dumppickedversions [versions] grokcore.component = diff --git a/setup.py b/setup.py index 469b25e..c8b1cbb 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ def read(*rnames): setup( name='grokcore.component', - version='2.5', + version='2.5.1.dev0', author='Grok Team', author_email='grok-dev@zope.org', url='http://grok.zope.org', diff --git a/src/grokcore/component/tests/test_grok.py b/src/grokcore/component/tests/test_grok.py index 4d7d681..9942a52 100644 --- a/src/grokcore/component/tests/test_grok.py +++ b/src/grokcore/component/tests/test_grok.py @@ -48,7 +48,7 @@ def suiteFromPackage(name): def test_suite(): suite = unittest.TestSuite() for name in ['adapter', 'directive', 'grokker', 'utility', 'view', - 'event', 'inherit', 'order', 'subscriptions']: + 'event', 'inherit', 'order', 'subscriptions', 'zcml']: suite.addTest(suiteFromPackage(name)) api = doctest.DocFileSuite('api.txt') diff --git a/src/grokcore/component/tests/zcml/__init__.py b/src/grokcore/component/tests/zcml/__init__.py new file mode 100644 index 0000000..a003759 --- /dev/null +++ b/src/grokcore/component/tests/zcml/__init__.py @@ -0,0 +1 @@ +# this is a package diff --git a/src/grokcore/component/tests/zcml/exclude.py b/src/grokcore/component/tests/zcml/exclude.py new file mode 100644 index 0000000..8e5effd --- /dev/null +++ b/src/grokcore/component/tests/zcml/exclude.py @@ -0,0 +1,34 @@ +""" +It allows to exclude a single package or module from beeing grokked. + +There is a NameError in `.excludepkg.sample` which is raised when this +module is not excluded: + +>>> xmlconfig.string(''' +... +... +... +... ''', context) +Traceback (most recent call last): +ZopeXMLConfigurationError: File "", line 4.6-4.31 + NameError: name 'asdf' is not defined + +Excluding `.excludepkg.sample` via ZCML allows to successfully grok the +module: + +>>> xmlconfig.string(''' +... +... +... +... ''', context) + + +""" + +from zope.configuration import config, xmlconfig +from grokcore.component.tests.zcml import excludepkg + +context = config.ConfigurationMachine() +xmlconfig.registerCommonDirectives(context) +context.package = excludepkg diff --git a/src/grokcore/component/tests/zcml/excludemany.py b/src/grokcore/component/tests/zcml/excludemany.py new file mode 100644 index 0000000..209808f --- /dev/null +++ b/src/grokcore/component/tests/zcml/excludemany.py @@ -0,0 +1,50 @@ +""" +It allows to exclude a many packages or modules from beeing grokked. + +These packages or modules can be specified using unix shell-style wildcards + +There is a NameError in `.excludemanypkg.file_1` which is raised when this +module is not excluded: + +>>> xmlconfig.string(''' +... +... +... +... ''', context) +Traceback (most recent call last): +ZopeXMLConfigurationError: File "", line 4.6-4.31 + NameError: name 'asdf' is not defined + +There is a NameError in `.excludemanypkg.test_asdf`, too which is raised when +this module is not excluded: + +>>> xmlconfig.string(''' +... +... +... +... ''', context) +Traceback (most recent call last): +ZopeXMLConfigurationError: File "", line 4.6-5.36 + NameError: name 'qwe' is not defined + + +Excluding both 'file_1` and `test_asdf`allows to successfully grok the module: + +>>> xmlconfig.string(''' +... +... +... +... ''', context) + + +""" + +from zope.configuration import config, xmlconfig +from grokcore.component.tests.zcml import excludemanypkg + +context = config.ConfigurationMachine() +xmlconfig.registerCommonDirectives(context) +context.package = excludemanypkg diff --git a/src/grokcore/component/tests/zcml/excludemanypkg/__init__.py b/src/grokcore/component/tests/zcml/excludemanypkg/__init__.py new file mode 100644 index 0000000..a003759 --- /dev/null +++ b/src/grokcore/component/tests/zcml/excludemanypkg/__init__.py @@ -0,0 +1 @@ +# this is a package diff --git a/src/grokcore/component/tests/zcml/excludemanypkg/file_1.py b/src/grokcore/component/tests/zcml/excludemanypkg/file_1.py new file mode 100644 index 0000000..0becf80 --- /dev/null +++ b/src/grokcore/component/tests/zcml/excludemanypkg/file_1.py @@ -0,0 +1 @@ +asdf # This leads to a NameError if exclude does not work correctly. diff --git a/src/grokcore/component/tests/zcml/excludemanypkg/test_asdf.py b/src/grokcore/component/tests/zcml/excludemanypkg/test_asdf.py new file mode 100644 index 0000000..3e15663 --- /dev/null +++ b/src/grokcore/component/tests/zcml/excludemanypkg/test_asdf.py @@ -0,0 +1 @@ +qwe # This leads to a NameError if exclude does not work correctly. diff --git a/src/grokcore/component/tests/zcml/excludepkg/__init__.py b/src/grokcore/component/tests/zcml/excludepkg/__init__.py new file mode 100644 index 0000000..a003759 --- /dev/null +++ b/src/grokcore/component/tests/zcml/excludepkg/__init__.py @@ -0,0 +1 @@ +# this is a package diff --git a/src/grokcore/component/tests/zcml/excludepkg/sample.py b/src/grokcore/component/tests/zcml/excludepkg/sample.py new file mode 100644 index 0000000..0becf80 --- /dev/null +++ b/src/grokcore/component/tests/zcml/excludepkg/sample.py @@ -0,0 +1 @@ +asdf # This leads to a NameError if exclude does not work correctly. diff --git a/src/grokcore/component/zcml.py b/src/grokcore/component/zcml.py index c8c6fb7..9f7d1d2 100644 --- a/src/grokcore/component/zcml.py +++ b/src/grokcore/component/zcml.py @@ -14,10 +14,11 @@ """Grok ZCML directives.""" from zope.interface import Interface -from zope.configuration.fields import GlobalObject +from zope.configuration.fields import GlobalObject, Tokens from zope.schema import TextLine import martian +import fnmatch class IGrokDirective(Interface): @@ -28,10 +29,12 @@ class IGrokDirective(Interface): description=u"The package or module to be analyzed by grok.", required=False) - exclude = TextLine( + exclude = Tokens( title=u"Exclude", - description=u"Name to exclude in the grokking process.", - required=False) + description=u"Names (which might contain unix shell-style wildcards) " + u"to be excluded in the grokking process.", + required=False, + value_type=TextLine()) # add a cleanup hook so that grok will bootstrap itself again whenever @@ -61,8 +64,12 @@ def do_grok(dotted_name, config, extra_exclude=None): if extra_exclude is not None: def exclude_filter(name): - return skip_tests(name) or extra_exclude == name - + if skip_tests(name): + return True + for exclude in extra_exclude: + if fnmatch.fnmatch(name, exclude): + return True + return False else: exclude_filter = skip_tests