PageRenderTime 329ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/_unsorted/core/importing/components.py

https://bitbucket.org/ericsnowcurrently/commonlib
Python | 256 lines | 239 code | 2 blank | 15 comment | 0 complexity | 98269b92ed65853a1066af10dd7c2ff4 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """core.importing.components module
  2. """
  3. """
  4. Module components act as extensions to the module associated with the
  5. component set. As a kind of plugin system, they allow a simple
  6. mechanism for extending a module.
  7. """
  8. """
  9. Making a Component Module
  10. =========================
  11. First, make a 'component module', which is a normal Python source file
  12. that contains whatever you want. This likely includes the objects you
  13. want to add to the core built-ins.
  14. Second, in the component module set __all__ to an iterable containing
  15. the names that should be bound in core.builtins.
  16. Third, put the component module into the _builtins directory of the core
  17. package.
  18. Restrictions
  19. ============
  20. The .builtins directory is not a package (no __init__.py) so component
  21. modules cannot be imported directly through the 'core' namespace.
  22. Rather, their only purpose is to extend core.builtins.
  23. If you would like to expose part of a component module outside
  24. core.builtins, put that part elsewhere in a normal module. Then you can
  25. import it into the component module, but still access it from outside
  26. core.builtins.
  27. Since .builtins is not a package, no component module will be loaded
  28. through the normal import mechanism. However, each will still be
  29. treated as a normal module.
  30. As such, component modules must have valid module names (non-keyword
  31. identifiers).
  32. Component modules are expected to be Python source files and are
  33. compiled with that in mind. The filename of each must have a '.py'
  34. extension.
  35. Any component module whose name starts with an underscore is ignored.
  36. Packages are not supported as component modules, so they are likewise
  37. ignored here.
  38. If a component module does not have an __all__ attibute, it is ignored.
  39. If any of the names in __all__ is already bound in core.builtins, the
  40. import of core.builtins will fail.
  41. Be careful of existing files when putting files into the .builtins
  42. directory.
  43. """
  44. """
  45. The core package makes use of a concept called component modules. These
  46. are sub-modules that contain the result of splitting a module up into
  47. cohesive parts. However, unlike with the similar approach of simply
  48. putting the sub-modules into a package, component modules are not
  49. exposed for direct import.
  50. Component modules are grouped together into component sets. Each set is
  51. realized by a directory whose name is a period followed by the name of
  52. the set (e.g. ".<set name>"). A strict component set directory will
  53. contain no __init__ module, but this is not enforced.
  54. A component set directory on its own is nothing more than an import-
  55. inaccessible directory. Using the set requires a module that
  56. So the quick way to identify a component set is the presence of a module
  57. file next to a component set directory with a matching name.
  58. """
  59. # XXX need handler support (a la PEP 302) for other manifestations of
  60. # component sets and module components (e.g. zip files, .pyc, etc.)
  61. __all__ = ("ComponentSet",)
  62. import os
  63. from os.path import join as pjoin, splitext, dirname
  64. from core import importing, builtins
  65. INDICATOR = "."
  66. class ModuleComponent(importing.Module):
  67. def __init__(self, name, setname, setpath):
  68. super(type(self), self).__init__(name)
  69. self.setname = setname
  70. self.setpath = setpath
  71. def default_names(self):
  72. return ()
  73. def load(self, names=None):
  74. if self.isloaded():
  75. return
  76. ns = exec_module(self.setpath, self.name)
  77. if names is None:
  78. names = ns.get("__all__", self.default_names)
  79. if not names:
  80. return
  81. try:
  82. self.namespace.update((k, ns[k]) for k in names)
  83. except KeyError, e:
  84. raise ImportError("cannot import name %s" % e[0])
  85. class ComponentNamespace(importing.ModuleNamespace):
  86. UNLOCKED_CLASS = ModuleComponent
  87. # XXX circular reference?
  88. ModuleComponent.NAMESPACE_CLASS = ComponentNamespace
  89. class ComponentSet(object):
  90. """A single component set.
  91. Parameters:
  92. name - the name of the component set.
  93. path - the path of the directory where the component set resides.
  94. """
  95. MODULE_CLASS = ComponentModule
  96. def __init__(self, name, path):
  97. self.name = name
  98. self.basepath = path
  99. self.path = pjoin(path, INDICATOR + name)
  100. self.main_component = "__%s__" % name
  101. self._names = ()
  102. self._components = None
  103. @classmethod
  104. def from_parent(cls, parent, name=None):
  105. """Expects parent to be a module object or the __file__ attr."""
  106. if isinstance(parent, Module):
  107. if name is None:
  108. name = parent.__name__.split(".")[-1]
  109. parent = parent.__file__
  110. elif name is None:
  111. name = importing.extract_name(parent)
  112. parent = importing.find_source(parent)
  113. return cls(name, dirname(parent))
  114. def __iter__(self):
  115. self._pull()
  116. return iter(self._components.values())
  117. def __getitem__(self, key):
  118. return self._components[key]
  119. @property
  120. def names(self):
  121. if hasattr(self, "_names"):
  122. return self._names
  123. self._pull()
  124. names = self._names = tuple(self._components)
  125. return names
  126. def _pull(self):
  127. if self._components is not None:
  128. return
  129. filenames = os.listdir(self.path)
  130. components = {}
  131. name = "%s.py" % self.main_component
  132. if name in filenames:
  133. components[name] = ComponentModule(name, self.name, self.path)
  134. for filename in filenames:
  135. if filename.startswith("_"):
  136. continue
  137. name, ext = splitext(filename)
  138. if not builtins.isidentifier(name):
  139. continue
  140. if ext != "py":
  141. continue
  142. components[name] = ComponentModule(name, self.name, self.path)
  143. self._components = components
  144. def load_all(self, bind=None, attach=None, on_collision="fail"):
  145. self._pull()
  146. if on_collision is None:
  147. cls = dict
  148. elif on_collision == "fail":
  149. cls = builtins.WriteOnceDict
  150. else:
  151. raise TypeError("unrecognized on_collision: %s" % on_collision)
  152. if bind is None:
  153. ns = cls()
  154. else:
  155. ns = cls(bind)
  156. moduleclass = type
  157. names = self.names[:]
  158. if names.pop(self.main_component, False):
  159. module = self[self.main_component]
  160. module.load()
  161. try:
  162. ns.update(module.namespace)
  163. except KeyError, e:
  164. raise ImportError(e[0])
  165. for name in names:
  166. module = self[name]
  167. module.load()
  168. try:
  169. ns.update(module.namespace)
  170. except KeyError, e:
  171. raise ImportError(e[0])
  172. if bind:
  173. bind.update(ns)
  174. if attach:
  175. for name in self.names:
  176. attach[name] = self[name]
  177. return ns
  178. @classmethod
  179. def load_from_module(module, bind=True):
  180. if hasattr(module, "__dict__"):
  181. module = module.__dict__
  182. components = cls.from_parent(module["__file__"], module["__name__"])
  183. if bind:
  184. names = components.load_all(bind=module)
  185. module["__all__"] += tuple(names)
  186. else:
  187. components.load_all()