diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-08-12 17:50:37 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-08-12 17:50:37 -0400 |
commit | f6198d9abf453182f4b111e0579a7a4ef1614e79 (patch) | |
tree | e258eafc9db70c4745d98a56b55b439732aebf91 /lib/sqlalchemy/util/langhelpers.py | |
parent | e8c2a2738b6c15cb12e7571b9e12c15cc2f200c9 (diff) | |
download | sqlalchemy-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.py | 102 |
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' |