[Zope-CVS] CVS: Products/Basket - README.txt:1.2 __init__.py:1.2
utils.py:1.2
Chris McDonough
chrism at plope.com
Mon Nov 7 14:43:07 EST 2005
Update of /cvs-repository/Products/Basket
In directory cvs.zope.org:/tmp/cvs-serv10464
Modified Files:
README.txt __init__.py utils.py
Log Message:
Commit a set of non-working code created since the first FXBG sprint.
=== Products/Basket/README.txt 1.1.1.1 => 1.2 ===
--- Products/Basket/README.txt:1.1.1.1 Mon Nov 7 13:47:23 2005
+++ Products/Basket/README.txt Mon Nov 7 14:42:35 2005
@@ -1,13 +1,27 @@
Overview
- Basket is a Zope product which allows you to employ the Python Egg
- format to deploy Zope products.
+ Basket is a Zope 2 product which allows you to employ the Python Egg
+ format to deploy other Zope 2 products.
Where Do Eggs Go?
- You can put Product eggs anywhere on your Zope instance's
- PYTHONPATH, but the conventional location for them is in
- INSTANCE_HOME/lib/python.
+ You can put Product eggs anywhere on your Zope 2 instance's
+ PYTHONPATH. A "safe" place to put them is
+ '$INSTANCE_HOME/lib/python' which is on the PYTHONPATH of every
+ post-2.6 Zope 2 installation.
+
+Definitions
+
+ Product -- A Python package that (optionally) includes an
+ initialization function which gets called at Zope startup time.
+ Products may be packaged as eggs using Basket; otherwise they are
+ typically packaged as tarballs which are meant to be unpacked in a
+ Zope 2 "Products" directory.
+
+ Egg Product -- a Product packaged up as part of a Product Distribution.
+
+ Product Distribution -- A Python "egg" which contains one or more
+ Zope 2 Products.
How Does Basket Determine Which Products To Install?
@@ -17,14 +31,17 @@
directories ending with the extension ".egg". These are known as
"distributions". For each of these distributions Basket finds, it
will introspect the content of the file or directory. If the file
- is a zip file and within its internal file structure contains a
- top-level namespace package named 'Products', this distribution
- will be considered a Product distribution and considered for
- initialization. The same action will happen if the .egg is a
- directory and contains a top-level namespace package named
- 'Products'. If two versions of the same distribution are found on
- the PYTHONPATH, Basket will prevent startup from occurring by
- raising a pkg_resources.VersionConflictError.
+ is a zip file and its egg metadata contains one or more
+ "zope2.initialize" "entry points", this distribution will be
+ considered to be a Product distribution and its constituent
+ Products will be considered for initialization. The same action
+ will happen if the .egg is a directory. If two versions of the
+ same distribution are found on the PYTHONPATH, Basket will prevent
+ startup from occurring by raising a
+ pkg_resources.VersionConflictError. If Basket detects a situation
+ in which two distinct Product distributions contain a Product that
+ has the same name (a case which is not caught by pkg_resources),
+ Basket will prevent startup by raising an exception.
Optional: Explicit
@@ -40,39 +57,190 @@
pkg_resources.DistribtionNotFound error. If the
PRODUCT_DISTRIBUTIONS.txt contains directives that cause two or
more versions of the same distribution to be considered, a
- pkg_resources.VersionConflictError will be raised.
+ pkg_resources.VersionConflictError will be raised. If Basket
+ detects a situation in which two distinct Product distributions
+ contain a Product that has the same name (a case which is not
+ caught by pkg_resources), Basket will prevent startup by raising
+ an exception.
How Do I Create Eggs that are Compatible with Basket?
The only hard-and-fast requirement for creating a Product
distribution is that you must create a "built
- egg":http://peak.telecommunity.com/DevCenter/PkgResources that
- includes a 'Products' "namespace package". Packages that exist
- beneath the Products namespace package should be "normal" Zope
- products, which are simply Python packages that optionally include
- an initialization function function which Zope calls during its
- startup process. If your product needs to be imported and
- initialized during Zope startup, you will need to define a
- 'zope2.initialize' "entry point" in your setup.py 'setup' call
- indicating which function should be called during initialization.
- Conventionally, this is 'Products.YourProductName:initialize'.
+ egg":http://peak.telecommunity.com/DevCenter/PkgResources. A
+ Product distribution is are simply a set of Python packages which
+ includes one or more initialization functions which Zope will call
+ during its startup process.
+
+ If your Product needs to be imported and initialized during Zope
+ startup (e.g. to register meta types or to show up in the Control
+ Panel Product list), you will need to define one or more "entry
+ points" of type 'zope2.initialize' in your setup.py 'setup' call
+ indicating which functions should be called during initialization.
+ If your product distribution contains only one Product, this "entry
+ point" is conventionally just 'SomePackageName:initialize'.
Products that are packaged as 'zip-safe' egg files must not attempt
to use Zope API functions that expect Product files to exist within
a filesystem structure.
+ A Product distribution may include a "Products" namespace package,
+ but it is not required. Each package within a Product distribution
+ which directly contains a 'zope2.initialize' function will be
+ considered a separate "Product". This means that the name of a
+ non-module package which directly contains the 'zope2.initialize'
+ function will be used as a Product name in Zope's control panel and
+ for legacy Zope API methods which expect to be able to use a Product
+ name to access constructor functions. Note that the behavior of
+ Products packaged within Product distributions differs slightly from
+ that of "legacy" Products inasmuch as "egg Products" are not
+ imported at Zope startup time and will not show up in the
+ ControlPanel list unless their packaging specifies a
+ 'zope2.initialize' entry point.
+
+Hello World (with Products Namespace Package)
+
+ filesystem layout::
+
+ |
+ |-- setup.py
+ |
+ |-- Products --
+ |
+ |-- __init__.py
+ |
+ |-- product1 --
+ |
+ |-- __init__.py
+
+ setup.py::
+
+ from setuptools import setup, find_packages
+ import ez_setup
+ ez_setup.use_setuptools()
+
+ setup(
+ name = 'product1',
+ version = '0.1',
+ packages = find_packages(),
+ namespace_packages=['Products'],
+ entry_points = {'zope2.initialize':
+ ['initialize=Products.product1:initialize']},
+ url = 'http://www.example.com/product1',
+ author = 'Joe Bloggs',
+ author_email = 'bloggs at example.com',
+ )
+
+ Products/__init__.py::
+
+ # this is a namespace package
+
+ Products/product1/__init__.py::
+
+ # this is a product initializer
+ def initialize(self):
+ return "product1 initialized"
+
+Hello World (no namespace packages)
+
+ filesystem layout::
+
+ |
+ |-- setup.py
+ |
+ |-- product1 --
+ |
+ |-- __init__.py
+
+ setup.py::
+
+ from setuptools import setup, find_packages
+ import ez_setup
+ ez_setup.use_setuptools()
+
+ setup(
+ name = 'product1',
+ version = '0.1',
+ packages = find_packages(),
+ entry_points = {'zope2.initialize':
+ ['initialize=product1:initialize']},
+ url = 'http://www.example.com/product1',
+ author = 'Joe Bloggs',
+ author_email = 'bloggs at example.com',
+ )
+
+ product1/__init__.py::
+
+ # this is a product initializer
+ def initialize(self):
+ return "product1 initialized"
+
+Multiple Products In A Single Distribution
+
+
+ filesystem layout::
+
+ |
+ |-- setup.py
+ |
+ |-- product1 --
+ | |
+ | |-- __init__.py
+ |
+ |-- product2 --
+ |
+ |-- __init__.py
+
+ setup.py::
+
+ from setuptools import setup, find_packages
+ import ez_setup
+ ez_setup.use_setuptools()
+
+ setup(
+ name = 'products1and2',
+ version = '0.1',
+ packages = find_packages(),
+ entry_points = {'zope2.initialize':
+ ['initialize1=product1:initialize',
+ 'initialize2=product2:initialize']},
+ url = 'http://www.example.com/products1and2',
+ author = 'Joe Bloggs',
+ author_email = 'bloggs at example.com',
+ )
+
+ product1/__init__.py::
+
+ # this is a product initializer
+ def initialize(self):
+ return "product1 initialized"
+
+ product2/__init__.py::
+
+ # this is a product initializer
+ def initialize(self):
+ return "product2 initialized"
+
+Building a Product Distribution
+
+ 'python setup.py bdist_egg'
+
+ The ".egg" file created in dist is the Product distribution. Refer
+ to the setuptools documentation for advanced options.
+
Forward Compatibility Notices
Note that the use of PRODUCT_DISTRIBUTIONS.txt may vanish in a later
- release of Basket in favor of a section within the Zope
- configuration file.
+ release of Basket in favor of a special section within the Zope 2
+ main configuration file.
The implicit load-all-product-distributions behavior may become
non-default in a later release in favor of using explicit
distribution naming.
-
-
-
-
-
+ The "Basket" product is a stopgap solution to the problem of being
+ able to package Zope products as Python eggs. Its functionality
+ will be foldeed into a later Zope release, at which point it will
+ cease to be useful and disappear. Therefore, you should not depend
+ on importing or otherwise using the "Products.Basket" package or its
+ contents anywhere in your own Zope code.
=== Products/Basket/__init__.py 1.1.1.1 => 1.2 ===
--- Products/Basket/__init__.py:1.1.1.1 Mon Nov 7 13:47:23 2005
+++ Products/Basket/__init__.py Mon Nov 7 14:42:35 2005
@@ -1,6 +1,8 @@
import sys
import os
import pkg_resources
+import inspect
+from utils import install_egg_product
class Basket(object):
def __init__(self):
@@ -29,10 +31,11 @@
self.preinitialize(pdist_fname)
data = []
points = pkg_resources.iter_entry_points('zope2.initialize')
+ meta_types = []
for point in points:
initialize = point.load()
- data.append(initialize(context))
- # we return data here for unit testing purposes
+ context = EggProductContext(product, app, package)
+ initialize(context)
return data
def product_distributions_by_dwim(self):
@@ -65,4 +68,20 @@
basket = Basket()
initialize = basket.initialize
+
+class Jackboot(object):
+ """
+ Nazi-like proxy for an object to replace this package in
+ sys.modules... Basket is born deprecated, and nothing but the
+ Zope core should try to use it... if you can get around this,
+ you DESERVE TO LOSE! ;-)
+ """
+ def __setattr__(self, k, v):
+ raise DeprecationWarning('This package is deprecated, '
+ 'do not monkey-patch it!')
+ def __getattribute__(self, k):
+ raise DeprecationWarning('This package is deprecated, do not import '
+ 'it directly!')
+
+
=== Products/Basket/utils.py 1.1.1.1 => 1.2 ===
--- Products/Basket/utils.py:1.1.1.1 Mon Nov 7 13:47:23 2005
+++ Products/Basket/utils.py Mon Nov 7 14:42:35 2005
@@ -1,15 +1,38 @@
import time
import pkg_resources
import os
+import re
+import sys
import Globals
from Globals import ImageFile
from Globals import DTMLFile
from Globals import package_home
+from Globals import InitializeClass
from OFS.content_types import guess_content_type
from App.Common import rfc1123_date
from App.special_dtml import defaultBindings
+from OFS.Application import pgetattr
+from OFS.Application import get_folder_permissions
+from App.ProductContext import ProductContext
+from App.Product import Product
+from App.Product import ihasattr
+from App.Product import doInstall
+from ZODB.POSException import ConflictError
+from OFS.Folder import Folder
+import transaction
+
+from OFS.ObjectManager import ObjectManager
+from AccessControl.Permission import registerPermissions
+from AccessControl.PermissionRole import PermissionRole
+from Interface.Implements import instancesOfObjectImplements
+import Products
+
+from zLOG import LOG
+from zLOG import ERROR
+
+_marker = ()
## from Products.PageTemplates.PageTemplateFile import PageTemplateFile
## from Products.PageTemplates.PageTemplateFile import XML_PREFIX_MAX_LENGTH
@@ -105,3 +128,445 @@
self.cook()
if not changed:
self.__changed__(0)
+
+class EggProduct(Product):
+ def __init__(self, id, title):
+ self.id=id
+ self.title=title
+
+ def manage_get_product_readme__(self):
+ for fname in ('README.txt', 'README.TXT', 'readme.txt'):
+ if pkg_resources.resource_exists(self.productname, fname):
+ return pkg_resources.resource_string(self.productname, fname)
+ return ''
+
+ def _readRefreshTxt(self, pid=None):
+ # egg products cannot be refreshed
+ return None
+
+ def manage_performRefresh(self, REQUEST=None):
+ """ """
+ # egg products can't be refreshed
+ return
+
+InitializeClass(EggProduct)
+
+class EggProductContext(object):
+ def __init__(self, app, package):
+ self.app = app
+ self.package = package
+ self.product = None
+
+ def createProductObject(self):
+ # Create a persisten object in the ControlPanel.Products area
+ # represeing a product packaged as an egg and set it as self.product
+ products = self.app.Control_Panel.Products
+ fver = ''
+
+
+
+ if hasattr(self.package, '__import_error__'):
+ ie = self.package.__import_error__
+ else:
+ ie = None
+
+ productname = self.product.__name__
+
+ # Retrieve version number from any suitable version.txt
+ for fname in ('version.txt', 'VERSION.txt', 'VERSION.TXT'):
+ if pkg_resources.resource_exists(productname, fname):
+ fver = pkg_resources.resource_string(productname, fname)
+ break
+
+ old=None
+
+ try:
+ if ihasattr(products, name):
+ old = getattr(products, name)
+ if ( ihasattr(old, 'version') and old.version == fver ):
+ if hasattr(old, 'import_error_') and \
+ old.import_error_==ie:
+ # Version hasn't changed. Don't reinitialize.
+ return old
+ except ConflictError:
+ raise
+ except:
+ pass
+
+ f=fver and (" (%s)" % fver)
+ product = EggProduct(name, 'Installed product %s%s' % (name,f))
+
+ if old is not None:
+ app._manage_remove_product_meta_type(product)
+ products._delObject(name)
+ for id, v in old.objectItems():
+ try: product._setObject(id, v)
+ except: pass
+
+ products._setObject(name, product)
+ product.icon='p_/InstalledProduct_icon'
+ product.version=fver
+ product.home=home
+
+ product.manage_options=(Folder.manage_options[0],) + \
+ tuple(Folder.manage_options[2:])
+ product._distribution=None
+ product.manage_distribution=None
+ product.thisIsAnInstalledProduct=1
+
+ if ie:
+ product.import_error_=ie
+ product.title='Broken product %s' % name
+ product.icon='p_/BrokenProduct_icon'
+ product.manage_options=(
+ {'label':'Traceback', 'action':'manage_traceback'},
+ )
+
+ for fname in ('README.txt', 'README.TXT', 'readme.txt'):
+ if pkg_resources.resource_exists(productname, fname):
+ product.manage_options=product.manage_options+(
+ {'label':'README', 'action':'manage_readme'},
+ )
+ break
+
+ if not doInstall():
+ transaction.abort()
+ return product
+
+ # Give the ZClass fixup code in Application
+ Globals.__disk_product_installed__=1
+ product.productname = productname
+ return product
+
+ def registerClass(self, instance_class=None, meta_type='',
+ permission=None, constructors=(),
+ icon=None, permissions=None, legacy=(),
+ visibility="Global", interfaces=_marker,
+ container_filter=None
+ ):
+ """Register a constructor
+
+ Keyword arguments are used to provide meta data:
+
+ instance_class -- The class of the object that will be created.
+
+ This is not currently used, but may be used in the future to
+ increase object mobility.
+
+ meta_type -- The kind of object being created
+ This appears in add lists. If not specified, then the class
+ meta_type will be used.
+
+ permission -- The permission name for the constructors.
+ If not specified, then a permission name based on the
+ meta type will be used.
+
+ constructors -- A list of constructor methods
+ A method can me a callable object with a __name__
+ attribute giving the name the method should have in the
+ product, or the method may be a tuple consisting of a
+ name and a callable object. The method must be picklable.
+
+ The first method will be used as the initial method called
+ when creating an object.
+
+ icon -- The name of an image file in the package to
+ be used for instances. Note that the class icon
+ attribute will be set automagically if an icon is
+ provided.
+
+ permissions -- Additional permissions to be registered
+ If not provided, then permissions defined in the
+ class will be registered.
+
+ legacy -- A list of legacy methods to be added to ObjectManager
+ for backward compatibility
+
+ visibility -- "Global" if the object is globally visible, None else
+
+ interfaces -- a list of the interfaces the object supports
+
+ container_filter -- function that is called with an ObjectManager
+ object as the only parameter, which should return a true object
+ if the object is happy to be created in that container. The
+ filter is called before showing ObjectManager's Add list,
+ and before pasting (after object copy or cut), but not
+ before calling an object's constructor.
+
+ """
+ app=self.app
+ pack=self.package
+ productObject=self.product
+
+ initial=constructors[0]
+ pid=productObject.id
+
+ if icon and instance_class is not None:
+ setattr(instance_class, 'icon', 'misc_/%s/%s' %
+ (pid, os.path.split(icon)[1]))
+
+ OM=ObjectManager
+
+ if permissions:
+ if isinstance(permissions, basestring): # You goofed it!
+ raise TypeError, ('Product context permissions should be a '
+ 'list of permissions not a string', permissions)
+ for p in permissions:
+ if isinstance(p, tuple):
+ p, default= p
+ registerPermissions(((p, (), default),))
+ else:
+ registerPermissions(((p, ()),))
+
+ ############################################################
+ # Constructor permission setup
+ if permission is None:
+ permission="Add %ss" % (meta_type or instance_class.meta_type)
+
+ if isinstance(permission, tuple):
+ permission, default = permission
+ else:
+ default = ('Manager',)
+
+ pr=PermissionRole(permission,default)
+ registerPermissions(((permission, (), default),))
+ ############################################################
+
+ for method in legacy:
+ if isinstance(method, tuple):
+ name, method = method
+ aliased = 1
+ else:
+ name=method.__name__
+ aliased = 0
+ if not OM.__dict__.has_key(name):
+ setattr(OM, name, method)
+ setattr(OM, name+'__roles__', pr)
+ if aliased:
+ # Set the unaliased method name and its roles
+ # to avoid security holes. XXX: All "legacy"
+ # methods need to be eliminated.
+ setattr(OM, method.__name__, method)
+ setattr(OM, method.__name__+'__roles__', pr)
+
+ if isinstance(initial, tuple):
+ name, initial = initial
+ else:
+ name = initial.__name__
+
+ fd=getattr(pack, '__FactoryDispatcher__', None)
+ if fd is None:
+ class __FactoryDispatcher__(FactoryDispatcher):
+ "Factory Dispatcher for a Specific Product"
+
+ fd = pack.__FactoryDispatcher__ = __FactoryDispatcher__
+
+ if not hasattr(pack, '_m'):
+ pack._m = AttrDict(fd)
+
+ m = pack._m
+
+ if interfaces is _marker:
+ if instance_class is None:
+ interfaces = ()
+ else:
+ interfaces = instancesOfObjectImplements(instance_class)
+
+ Products.meta_types=Products.meta_types+(
+ { 'name': meta_type or instance_class.meta_type,
+ 'action': ('manage_addProduct/%s/%s' % (pid, name)),
+ 'product': pid,
+ 'permission': permission,
+ 'visibility': visibility,
+ 'interfaces': interfaces,
+ 'instance': instance_class,
+ 'container_filter': container_filter
+ },)
+
+ m[name]=initial
+ m[name+'__roles__']=pr
+
+ for method in constructors[1:]:
+ if isinstance(method, tuple):
+ name, method = method
+ else:
+ name=os.path.split(method.__name__)[-1]
+ if not productObject.__dict__.has_key(name):
+ m[name]=method
+ m[name+'__roles__']=pr
+
+ if icon:
+ name=os.path.split(icon)[1]
+ icon=ImageResource(icon, self.__pack.__dict__)
+ icon.__roles__=None
+ if not hasattr(OFS.misc_.misc_, pid):
+ setattr(OFS.misc_.misc_, pid, OFS.misc_.Misc_(pid, {}))
+ getattr(OFS.misc_.misc_, pid)[name]=icon
+
+ def registerBaseClass(self, base_class, meta_type=None):
+ #
+ # Convenience method, now deprecated -- clients should
+ # call 'ZClasses.createZClassForBase()' themselves at
+ # module import time, passing 'globals()', so that the
+ # ZClass will be available immediately.
+ #
+ Z = ZClasses.createZClassForBase( base_class, self.__pack )
+ return Z
+
+
+ def install(self, app, product, initializer, raise_exc=0, log_exc=1):
+
+ folder_permissions = get_folder_permissions()
+
+ global_dict = globals()
+
+ __traceback_info__ = product_name = product.__name__
+
+ try:
+ # Install items into the misc_ namespace, used by products
+ # and the framework itself to store common static resources
+ # like icon images.
+ misc_=pgetattr(product, 'misc_', {})
+ if misc_:
+ if isinstance(misc_, dict):
+ misc_=Misc_(product_name, misc_)
+ Application.misc_.__dict__[product_name]=misc_
+
+ # Here we create a ProductContext object which contains
+ # information about the product and provides an interface
+ # for registering things like classes and help topics that
+ # should be associated with that product. Products are
+ # expected to implement a method named 'initialize' in
+ # their __init__.py that takes the ProductContext as an
+ # argument.
+ productObject = initialize_egg_product(product, product_name, app)
+ context = EggProductContext(productObject, app, product)
+
+ data = initializer(context)
+
+ # Support old-style product metadata. Older products may
+ # define attributes to name their permissions, meta_types,
+ # constructors, etc.
+ permissions={}
+ new_permissions={}
+ for p in pgetattr(product, '__ac_permissions__', ()):
+ permission, names, default = (
+ tuple(p)+('Manager',))[:3]
+ if names:
+ for name in names:
+ permissions[name]=permission
+ elif not folder_permissions.has_key(permission):
+ new_permissions[permission]=()
+
+ for meta_type in pgetattr(product, 'meta_types', ()):
+ # Modern product initialization via a ProductContext
+ # adds 'product' and 'permission' keys to the meta_type
+ # mapping. We have to add these here for old products.
+ pname=permissions.get(meta_type['action'], None)
+ if pname is not None:
+ meta_type['permission']=pname
+ meta_type['product']=productObject.id
+ meta_type['visibility'] = 'Global'
+
+ for name,method in pgetattr(
+ product, 'methods', {}).items():
+ if not hasattr(Folder, name):
+ setattr(Folder, name, method)
+ if name[-9:]!='__roles__': # not Just setting roles
+ if (permissions.has_key(name) and
+ not folder_permissions.has_key(
+ permissions[name])):
+ permission=permissions[name]
+ if new_permissions.has_key(permission):
+ new_permissions[permission].append(name)
+ else:
+ new_permissions[permission]=[name]
+
+ if new_permissions:
+ new_permissions=new_permissions.items()
+ for permission, names in new_permissions:
+ folder_permissions[permission]=names
+ new_permissions.sort()
+ Folder.__ac_permissions__=tuple(
+ list(Folder.__ac_permissions__)+new_permissions)
+
+ if not doInstall():
+ transaction().abort()
+ else:
+ transaction.get().note('Installed product '+product_name)
+ transaction.commit()
+
+ except:
+ if log_exc:
+ LOG('Zope',ERROR,'Couldn\'t install %s' % product_name,
+ error=sys.exc_info())
+ transaction.abort()
+ if raise_exc:
+ raise
+
+ return data
+
+ def initialize_egg_product(self, productp, name, app):
+
+ def registerHelp(self, *arg, **kw):
+ return # this is so not worth it
+
+ def getProductHelp(self):
+ """
+ Returns the ProductHelp associated with the current Product.
+ """
+ return None
+
+ def registerHelpTopic(self, id, topic):
+ """
+ Register a Help Topic for a product.
+ """
+ pass
+
+ def registerHelpTitle(self, title):
+ """
+ Sets the title of the Product's Product Help
+ """
+ pass
+
+
+def import_product(product_dir, product_name, raise_exc=0, log_exc=1):
+ path_join=os.path.join
+ isdir=os.path.isdir
+ exists=os.path.exists
+ _st=type('')
+ global_dict=globals()
+ silly=('__doc__',)
+ modules=sys.modules
+ have_module=modules.has_key
+
+ try:
+ package_dir=path_join(product_dir, product_name)
+ if not isdir(package_dir): return
+ if not exists(path_join(package_dir, '__init__.py')):
+ if not exists(path_join(package_dir, '__init__.pyc')):
+ if not exists(path_join(package_dir, '__init__.pyo')):
+ return
+
+ pname="Products.%s" % product_name
+ try:
+ product=__import__(pname, global_dict, global_dict, silly)
+ if hasattr(product, '__module_aliases__'):
+ for k, v in product.__module_aliases__:
+ if not have_module(k):
+ if type(v) is _st and have_module(v): v=modules[v]
+ modules[k]=v
+ except:
+ exc = sys.exc_info()
+ if log_exc:
+ LOG('Zope', ERROR, 'Could not import %s' % pname,
+ error=exc)
+ f=StringIO()
+ traceback.print_exc(100,f)
+ f=f.getvalue()
+ try: modules[pname].__import_error__=f
+ except: pass
+ if raise_exc:
+ raise exc[0], exc[1], exc[2]
+ finally:
+ exc = None
More information about the Zope-CVS
mailing list