summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/util/langhelpers.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-08-12 17:50:37 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2013-08-12 17:50:37 -0400
commitf6198d9abf453182f4b111e0579a7a4ef1614e79 (patch)
treee258eafc9db70c4745d98a56b55b439732aebf91 /lib/sqlalchemy/util/langhelpers.py
parente8c2a2738b6c15cb12e7571b9e12c15cc2f200c9 (diff)
downloadsqlalchemy-f6198d9abf453182f4b111e0579a7a4ef1614e79.tar.gz
- A large refactoring of the ``sqlalchemy.sql`` package has reorganized
the import structure of many core modules. ``sqlalchemy.schema`` and ``sqlalchemy.types`` remain in the top-level package, but are now just lists of names that pull from within ``sqlalchemy.sql``. Their implementations are now broken out among ``sqlalchemy.sql.type_api``, ``sqlalchemy.sql.sqltypes``, ``sqlalchemy.sql.schema`` and ``sqlalchemy.sql.ddl``, the last of which was moved from ``sqlalchemy.engine``. ``sqlalchemy.sql.expression`` is also a namespace now which pulls implementations mostly from ``sqlalchemy.sql.elements``, ``sqlalchemy.sql.selectable``, and ``sqlalchemy.sql.dml``. Most of the "factory" functions used to create SQL expression objects have been moved to classmethods or constructors, which are exposed in ``sqlalchemy.sql.expression`` using a programmatic system. Care has been taken such that all the original import namespaces remain intact and there should be no impact on any existing applications. The rationale here was to break out these very large modules into smaller ones, provide more manageable lists of function names, to greatly reduce "import cycles" and clarify the up-front importing of names, and to remove the need for redundant functions and documentation throughout the expression package.
Diffstat (limited to 'lib/sqlalchemy/util/langhelpers.py')
-rw-r--r--lib/sqlalchemy/util/langhelpers.py102
1 files changed, 98 insertions, 4 deletions
diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py
index d8e469a2b..e10a10e27 100644
--- a/lib/sqlalchemy/util/langhelpers.py
+++ b/lib/sqlalchemy/util/langhelpers.py
@@ -107,6 +107,30 @@ def decorator(target):
return update_wrapper(decorate, target)
+def public_factory(target):
+ """Produce a wrapping function for the given cls or classmethod.
+
+ Rationale here is so that the __init__ method of the
+ class can serve as documentation for the function.
+
+ """
+ if isinstance(target, type):
+ fn = target.__init__
+ callable_ = target
+ else:
+ fn = callable_ = target
+ spec = compat.inspect_getfullargspec(fn)
+ del spec[0][0]
+ #import pdb
+ #pdb.set_trace()
+ metadata = format_argspec_plus(spec, grouped=False)
+ code = 'lambda %(args)s: cls(%(apply_kw)s)' % metadata
+ decorated = eval(code, {'cls': callable_, 'symbol': symbol})
+ decorated.__doc__ = fn.__doc__
+ return decorated
+ #return update_wrapper(decorated, fn)
+
+
class PluginLoader(object):
def __init__(self, group, auto_fn=None):
@@ -619,7 +643,11 @@ class memoized_property(object):
return result
def _reset(self, obj):
- obj.__dict__.pop(self.__name__, None)
+ memoized_property.reset(obj, self.__name__)
+
+ @classmethod
+ def reset(cls, obj, name):
+ obj.__dict__.pop(name, None)
class memoized_instancemethod(object):
@@ -698,15 +726,26 @@ class importlater(object):
_unresolved = set()
+ _by_key = {}
+
+ def __new__(cls, path, addtl):
+ key = path + "." + addtl
+ if key in importlater._by_key:
+ return importlater._by_key[key]
+ else:
+ importlater._by_key[key] = imp = object.__new__(cls)
+ return imp
+
def __init__(self, path, addtl):
self._il_path = path
self._il_addtl = addtl
importlater._unresolved.add(self)
@classmethod
- def resolve_all(cls):
+ def resolve_all(cls, path):
for m in list(importlater._unresolved):
- m._resolve()
+ if m._full_path.startswith(path):
+ m._resolve()
@property
def _full_path(self):
@@ -742,6 +781,61 @@ class importlater(object):
self.__dict__[key] = attr
return attr
+def dependencies(*deps):
+ """Apply imported dependencies as arguments to a function.
+
+ E.g.::
+
+ @util.dependencies(
+ "sqlalchemy.sql.widget",
+ "sqlalchemy.engine.default"
+ );
+ def some_func(self, widget, default, arg1, arg2, **kw):
+ # ...
+
+ Rationale is so that the impact of a dependency cycle can be
+ associated directly with the few functions that cause the cycle,
+ and not pollute the module-level namespace.
+
+ """
+ import_deps = []
+ for dep in deps:
+ tokens = dep.split(".")
+ import_deps.append(
+ importlater(
+ ".".join(tokens[0:-1]),
+ tokens[-1]
+ )
+ )
+
+ def decorate(fn):
+ spec = compat.inspect_getfullargspec(fn)
+
+ spec_zero = list(spec[0])
+ hasself = spec_zero[0] in ('self', 'cls')
+
+ for i in range(len(import_deps)):
+ spec[0][i + (1 if hasself else 0)] = "import_deps[%r]" % i
+
+ inner_spec = format_argspec_plus(spec, grouped=False)
+
+ for impname in import_deps:
+ del spec_zero[1 if hasself else 0]
+ spec[0][:] = spec_zero
+
+ outer_spec = format_argspec_plus(spec, grouped=False)
+
+ code = 'lambda %(args)s: fn(%(apply_kw)s)' % {
+ "args": outer_spec['args'],
+ "apply_kw": inner_spec['apply_kw']
+ }
+
+ decorated = eval(code, locals())
+ decorated.__defaults__ = getattr(fn, 'im_func', fn).__defaults__
+ return update_wrapper(decorated, fn)
+ return decorate
+
+
# from paste.deploy.converters
def asbool(obj):
if isinstance(obj, compat.string_types):
@@ -943,7 +1037,7 @@ class _symbol(int):
return repr(self)
def __repr__(self):
- return "<symbol '%s>" % self.name
+ return "symbol(%r)" % self.name
_symbol.__name__ = 'symbol'