diff options
| author | Hajime Nakagami <nakagami@gmail.com> | 2013-04-06 17:45:42 +0900 |
|---|---|---|
| committer | Hajime Nakagami <nakagami@gmail.com> | 2013-04-06 17:45:42 +0900 |
| commit | b4da894d02f2e99f0a604ad45f6c0e5058392d81 (patch) | |
| tree | 1eb9887480bec5224924e379b2e0f03d10ce8ebb /lib/sqlalchemy | |
| parent | 7a4945ebdd1358502e1872560b97f1344b8c53db (diff) | |
| parent | 6bdd3bb93fd18a4aec54ee2a836875a922dcaab3 (diff) | |
| download | sqlalchemy-b4da894d02f2e99f0a604ad45f6c0e5058392d81.tar.gz | |
merge from default
Diffstat (limited to 'lib/sqlalchemy')
39 files changed, 376 insertions, 318 deletions
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 2352f1308..6e924ea9d 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -120,7 +120,7 @@ from .engine import create_engine, engine_from_config __all__ = sorted(name for name, obj in locals().items() if not (name.startswith('_') or _inspect.ismodule(obj))) -__version__ = '0.8.0' +__version__ = '0.8.1' del _inspect, sys diff --git a/lib/sqlalchemy/dialects/__init__.py b/lib/sqlalchemy/dialects/__init__.py index fbbff153c..7f5d34707 100644 --- a/lib/sqlalchemy/dialects/__init__.py +++ b/lib/sqlalchemy/dialects/__init__.py @@ -18,7 +18,6 @@ __all__ = ( from .. import util - def _auto_fn(name): """default dialect importer. diff --git a/lib/sqlalchemy/dialects/firebird/kinterbasdb.py b/lib/sqlalchemy/dialects/firebird/kinterbasdb.py index 206dbf38b..d581f799a 100644 --- a/lib/sqlalchemy/dialects/firebird/kinterbasdb.py +++ b/lib/sqlalchemy/dialects/firebird/kinterbasdb.py @@ -50,8 +50,8 @@ __ http://kinterbasdb.sourceforge.net/dist_docs/usage.html#special_issue_concurr from .base import FBDialect, FBExecutionContext from ... import util, types as sqltypes -from ...util.compat import decimal from re import match +import decimal class _FBNumeric_kinterbasdb(sqltypes.Numeric): diff --git a/lib/sqlalchemy/dialects/mssql/pymssql.py b/lib/sqlalchemy/dialects/mssql/pymssql.py index 6f6d3b01f..b916612fb 100644 --- a/lib/sqlalchemy/dialects/mssql/pymssql.py +++ b/lib/sqlalchemy/dialects/mssql/pymssql.py @@ -54,7 +54,7 @@ class MSDialect_pymssql(MSDialect): module = __import__('pymssql') # pymmsql doesn't have a Binary method. we use string # TODO: monkeypatching here is less than ideal - module.Binary = str + module.Binary = lambda x: x if hasattr(x, 'decode') else str(x) client_ver = tuple(int(x) for x in module.__version__.split(".")) if client_ver < (1, ): @@ -85,6 +85,8 @@ class MSDialect_pymssql(MSDialect): def is_disconnect(self, e, connection, cursor): for msg in ( + "Adaptive Server connection timed out", + "message 20003", # connection timeout "Error 10054", "Not connected to any MS SQL server", "Connection is closed" diff --git a/lib/sqlalchemy/dialects/mssql/pyodbc.py b/lib/sqlalchemy/dialects/mssql/pyodbc.py index 8854d1caa..beb6066f5 100644 --- a/lib/sqlalchemy/dialects/mssql/pyodbc.py +++ b/lib/sqlalchemy/dialects/mssql/pyodbc.py @@ -114,7 +114,7 @@ for unix + PyODBC. from .base import MSExecutionContext, MSDialect from ...connectors.pyodbc import PyODBCConnector from ... import types as sqltypes, util -from ...util.compat import decimal +import decimal class _MSNumeric_pyodbc(sqltypes.Numeric): diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index 62598ad00..f83020d93 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -2409,7 +2409,6 @@ class MySQLTableDefinitionParser(object): state.constraints.append(spec) else: pass - return state def _parse_constraints(self, line): diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py index fd9fea878..b8f7439f5 100644 --- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py +++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py @@ -190,7 +190,7 @@ from ...engine import result as _result from sqlalchemy import types as sqltypes, util, exc, processors import random import collections -from sqlalchemy.util.compat import decimal +import decimal import re diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index a7a9e65ce..c59caff8d 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -1030,6 +1030,15 @@ class PGCompiler(compiler.SQLCompiler): field, self.process(expr)) + def visit_substring_func(self, func, **kw): + s = self.process(func.clauses.clauses[0], **kw) + start = self.process(func.clauses.clauses[1], **kw) + if len(func.clauses.clauses) > 2: + length = self.process(func.clauses.clauses[2], **kw) + return "SUBSTRING(%s FROM %s FOR %s)" % (s, start, length) + else: + return "SUBSTRING(%s FROM %s)" % (s, start) + class PGDDLCompiler(compiler.DDLCompiler): def get_column_specification(self, column, **kwargs): colspec = self.preparer.format_column(column) @@ -1042,8 +1051,7 @@ class PGDDLCompiler(compiler.DDLCompiler): ( isinstance(column.default, schema.Sequence) and column.default.optional - ) - ): + )): if isinstance(impl_type, sqltypes.BigInteger): colspec += " BIGSERIAL" else: diff --git a/lib/sqlalchemy/dialects/postgresql/hstore.py b/lib/sqlalchemy/dialects/postgresql/hstore.py index 157e03fd5..e555a1afd 100644 --- a/lib/sqlalchemy/dialects/postgresql/hstore.py +++ b/lib/sqlalchemy/dialects/postgresql/hstore.py @@ -6,7 +6,7 @@ import re -from .base import ARRAY +from .base import ARRAY, ischema_names from ... import types as sqltypes from ...sql import functions as sqlfunc from ...sql.operators import custom_op @@ -276,6 +276,9 @@ class HSTORE(sqltypes.Concatenable, sqltypes.TypeEngine): return process +ischema_names['hstore'] = HSTORE + + class hstore(sqlfunc.GenericFunction): """Construct an hstore value within a SQL expression using the Postgresql ``hstore()`` function. diff --git a/lib/sqlalchemy/dialects/postgresql/pg8000.py b/lib/sqlalchemy/dialects/postgresql/pg8000.py index 214db348c..0e503746c 100644 --- a/lib/sqlalchemy/dialects/postgresql/pg8000.py +++ b/lib/sqlalchemy/dialects/postgresql/pg8000.py @@ -27,7 +27,7 @@ yet. """ from ... import util, exc -from ...util.compat import decimal +import decimal from ... import processors from ... import types as sqltypes from .base import PGDialect, \ diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index 649a95ee6..1f118067f 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -147,7 +147,7 @@ import re import logging from ... import util, exc -from ...util.compat import decimal +import decimal from ... import processors from ...engine import result as _result from ...sql import expression diff --git a/lib/sqlalchemy/dialects/sybase/pyodbc.py b/lib/sqlalchemy/dialects/sybase/pyodbc.py index 644f4edb1..283c60da3 100644 --- a/lib/sqlalchemy/dialects/sybase/pyodbc.py +++ b/lib/sqlalchemy/dialects/sybase/pyodbc.py @@ -36,7 +36,7 @@ from sqlalchemy.dialects.sybase.base import SybaseDialect,\ SybaseExecutionContext from sqlalchemy.connectors.pyodbc import PyODBCConnector from sqlalchemy import types as sqltypes, processors -from sqlalchemy.util.compat import decimal +import decimal class _SybNumeric_pyodbc(sqltypes.Numeric): diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 1db0f2ce4..abb9f0fc3 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -34,6 +34,10 @@ class DefaultDialect(interfaces.Dialect): preparer = compiler.IdentifierPreparer supports_alter = True + # the first value we'd get for an autoincrement + # column. + default_sequence_base = 1 + # most DBAPIs happy with this for execute(). # not cx_oracle. execute_sequence_format = tuple @@ -679,7 +683,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext): lastrowid = proc(lastrowid) self.inserted_primary_key = [ - c is autoinc_col and lastrowid or v + lastrowid if c is autoinc_col else v for c, v in zip( table.primary_key, self.inserted_primary_key) diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 891aef862..3eda127fd 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -174,6 +174,49 @@ class QueryableAttribute(interfaces._MappedAttribute, # TODO: conditionally attach this method based on clause_element ? return self + + @util.memoized_property + def info(self): + """Return the 'info' dictionary for the underlying SQL element. + + The behavior here is as follows: + + * If the attribute is a column-mapped property, i.e. + :class:`.ColumnProperty`, which is mapped directly + to a schema-level :class:`.Column` object, this attribute + will return the :attr:`.SchemaItem.info` dictionary associated + with the core-level :class:`.Column` object. + + * If the attribute is a :class:`.ColumnProperty` but is mapped to + any other kind of SQL expression other than a :class:`.Column`, + the attribute will refer to the :attr:`.MapperProperty.info` dictionary + associated directly with the :class:`.ColumnProperty`, assuming the SQL + expression itself does not have it's own ``.info`` attribute + (which should be the case, unless a user-defined SQL construct + has defined one). + + * If the attribute refers to any other kind of :class:`.MapperProperty`, + including :class:`.RelationshipProperty`, the attribute will refer + to the :attr:`.MapperProperty.info` dictionary associated with + that :class:`.MapperProperty`. + + * To access the :attr:`.MapperProperty.info` dictionary of the :class:`.MapperProperty` + unconditionally, including for a :class:`.ColumnProperty` that's + associated directly with a :class:`.schema.Column`, the attribute + can be referred to using :attr:`.QueryableAttribute.property` + attribute, as ``MyClass.someattribute.property.info``. + + .. versionadded:: 0.8.0 + + .. seealso:: + + :attr:`.SchemaItem.info` + + :attr:`.MapperProperty.info` + + """ + return self.comparator.info + @util.memoized_property def parent(self): """Return an inspection instance representing the parent. diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index b3f7baf0f..62cdb2710 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -209,6 +209,12 @@ class MapperProperty(_MappedAttribute, _InspectionAttr): .. versionadded:: 0.8 Added support for .info to all :class:`.MapperProperty` subclasses. + .. seealso:: + + :attr:`.QueryableAttribute.info` + + :attr:`.SchemaItem.info` + """ return {} @@ -390,6 +396,10 @@ class PropComparator(operators.ColumnOperators): return self.__class__(self.prop, self._parentmapper, adapter) + @util.memoized_property + def info(self): + return self.property.info + @staticmethod def any_op(a, b, **kwargs): return a.any(b, **kwargs) diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 4e7b4d272..d258a20b6 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -2002,10 +2002,20 @@ class Mapper(_InspectionAttr): @_memoized_configured_property def _sorted_tables(self): table_to_mapper = {} + for mapper in self.base_mapper.self_and_descendants: for t in mapper.tables: table_to_mapper.setdefault(t, mapper) + extra_dependencies = [] + for table, mapper in table_to_mapper.items(): + super_ = mapper.inherits + if super_: + extra_dependencies.extend([ + (super_table, table) + for super_table in super_.tables + ]) + def skip(fk): # attempt to skip dependencies that are not # significant to the inheritance chain @@ -2017,7 +2027,7 @@ class Mapper(_InspectionAttr): if parent is not None and \ dep is not None and \ dep is not parent and \ - dep.inherit_condition is not None: + dep.inherit_condition is not None: cols = set(sql_util.find_columns(dep.inherit_condition)) if parent.inherit_condition is not None: cols = cols.union(sql_util.find_columns( @@ -2028,7 +2038,9 @@ class Mapper(_InspectionAttr): return False sorted_ = sql_util.sort_tables(table_to_mapper.iterkeys(), - skip_fn=skip) + skip_fn=skip, + extra_dependencies=extra_dependencies) + ret = util.OrderedDict() for t in sorted_: ret[t] = table_to_mapper[t] @@ -2224,7 +2236,7 @@ def _event_on_resurrect(state): state, state.dict, col, val) -class _ColumnMapping(util.py25_dict): +class _ColumnMapping(dict): """Error reporting helper for mapper._columntoproperty.""" def __init__(self, mapper): diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index d9dfff77d..e225a7c83 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -154,7 +154,7 @@ def _organize_states_for_save(base_mapper, states, uowtransaction): # with the same identity key already exists as persistent. # convert to an UPDATE if so. if not has_identity and \ - instance_key in uowtransaction.session.identity_map: + instance_key in uowtransaction.session.identity_map: instance = \ uowtransaction.session.identity_map[instance_key] existing = attributes.instance_state(instance) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 9d977b221..9f8721de9 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -193,6 +193,14 @@ class ColumnProperty(StrategizedProperty): "parententity": self._parentmapper, "parentmapper": self._parentmapper}) + @util.memoized_property + def info(self): + ce = self.__clause_element__() + try: + return ce.info + except AttributeError: + return self.prop.info + def __getattr__(self, key): """proxy attribute access down to the mapped column. diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index fd0c54d88..9e44e01f7 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -352,7 +352,7 @@ class JoinCondition(object): return if "foreign" not in binary.left._annotations and \ - "foreign" not in binary.right._annotations: + "foreign" not in binary.right._annotations: col = is_foreign(binary.left, binary.right) if col is not None: if col.compare(binary.left): @@ -451,12 +451,11 @@ class JoinCondition(object): def visit_binary(binary): equated = binary.left.compare(binary.right) if isinstance(binary.left, expression.ColumnClause) and \ - isinstance(binary.right, expression.ColumnClause): + isinstance(binary.right, expression.ColumnClause): # assume one to many - FKs are "remote" if fn(binary.left): binary.left = binary.left._annotate({"remote": True}) - if fn(binary.right) and \ - not equated: + if fn(binary.right) and not equated: binary.right = binary.right._annotate( {"remote": True}) else: @@ -507,9 +506,9 @@ class JoinCondition(object): def proc_left_right(left, right): if isinstance(left, expression.ColumnClause) and \ - isinstance(right, expression.ColumnClause): + isinstance(right, expression.ColumnClause): if self.child_selectable.c.contains_column(right) and \ - self.parent_selectable.c.contains_column(left): + self.parent_selectable.c.contains_column(left): right = right._annotate({"remote": True}) else: self._warn_non_column_elements() @@ -532,8 +531,7 @@ class JoinCondition(object): not self.parent_local_selectable.c.\ contains_column(element) or self.child_local_selectable.c.\ - contains_column(element) - ): + contains_column(element)): return element._annotate({"remote": True}) self.primaryjoin = visitors.replacement_traverse( self.primaryjoin, {}, repl) @@ -568,7 +566,7 @@ class JoinCondition(object): def locals_(elem): if "remote" not in elem._annotations and \ - elem in local_side: + elem in local_side: return elem._annotate({"local": True}) self.primaryjoin = visitors.replacement_traverse( self.primaryjoin, {}, locals_ @@ -603,7 +601,7 @@ class JoinCondition(object): can_sync = bool(self.secondary_synchronize_pairs) if self.support_sync and can_sync or \ - (not self.support_sync and has_foreign): + (not self.support_sync and has_foreign): return # from here below is just determining the best error message @@ -685,8 +683,7 @@ class JoinCondition(object): "Ensure that only those columns referring " "to a parent column are marked as foreign, " "either via the foreign() annotation or " - "via the foreign_keys argument." - % self.prop) + "via the foreign_keys argument." % self.prop) elif onetomany_fk: self.direction = ONETOMANY elif manytoone_fk: @@ -716,14 +713,14 @@ class JoinCondition(object): def visit_binary(binary, left, right): if "remote" in right._annotations and \ "remote" not in left._annotations and \ - self.can_be_synced_fn(left): + self.can_be_synced_fn(left): lrp.add((left, right)) elif "remote" in left._annotations and \ "remote" not in right._annotations and \ - self.can_be_synced_fn(right): + self.can_be_synced_fn(right): lrp.add((right, left)) if binary.operator is operators.eq and \ - self.can_be_synced_fn(left, right): + self.can_be_synced_fn(left, right): if "foreign" in right._annotations: collection.append((left, right)) elif "foreign" in left._annotations: diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index cc9dd6ba5..f3b8e271d 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -1265,3 +1265,40 @@ def attribute_str(instance, attribute): def state_attribute_str(state, attribute): return state_str(state) + "." + attribute + + +def randomize_unitofwork(): + """Use random-ordering sets within the unit of work in order + to detect unit of work sorting issues. + + This is a utility function that can be used to help reproduce + inconsistent unit of work sorting issues. For example, + if two kinds of objects A and B are being inserted, and + B has a foreign key reference to A - the A must be inserted first. + However, if there is no relationship between A and B, the unit of work + won't know to perform this sorting, and an operation may or may not + fail, depending on how the ordering works out. Since Python sets + and dictionaries have non-deterministic ordering, such an issue may + occur on some runs and not on others, and in practice it tends to + have a great dependence on the state of the interpreter. This leads + to so-called "heisenbugs" where changing entirely irrelevant aspects + of the test program still cause the failure behavior to change. + + By calling ``randomize_unitofwork()`` when a script first runs, the + ordering of a key series of sets within the unit of work implementation + are randomized, so that the script can be minimized down to the fundamental + mapping and operation that's failing, while still reproducing the issue + on at least some runs. + + This utility is also available when running the test suite via the + ``--reversetop`` flag. + + .. versionadded:: 0.8.1 created a standalone version of the + ``--reversetop`` feature. + + """ + from sqlalchemy.orm import unitofwork, session, mapper, dependency + from sqlalchemy.util import topological + from sqlalchemy.testing.util import RandomSet + topological.set = unitofwork.set = session.set = mapper.set = \ + dependency.set = RandomSet diff --git a/lib/sqlalchemy/pool.py b/lib/sqlalchemy/pool.py index f72082ceb..501b6d2a0 100644 --- a/lib/sqlalchemy/pool.py +++ b/lib/sqlalchemy/pool.py @@ -736,8 +736,8 @@ class QueuePool(Pool): self._overflow = 0 - pool_size self._max_overflow = max_overflow self._timeout = timeout - self._overflow_lock = self._max_overflow > -1 and \ - threading.Lock() or DummyLock() + self._overflow_lock = threading.Lock() if self._max_overflow > -1 \ + else DummyLock() def _do_return_conn(self, conn): try: diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index 9d14bd3ca..9a07b9de4 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -93,14 +93,13 @@ def _get_table_key(name, schema): def _validate_dialect_kwargs(kwargs, name): # validate remaining kwargs that they all specify DB prefixes - if len([k for k in kwargs - if not re.match( - r'^(?:%s)_' % - '|'.join(dialects.__all__), k - ) - ]): - raise TypeError( - "Invalid argument(s) for %s: %r" % (name, kwargs.keys())) + + for k in kwargs: + m = re.match('^(.+?)_.*', k) + if m is None: + raise TypeError("Additional arguments should be " + "named <dialectname>_<argument>, got '%s'" % k) + inspection._self_inspects(SchemaItem) @@ -2025,7 +2024,7 @@ class ColumnCollectionMixin(object): for c in columns] if self._pending_colargs and \ isinstance(self._pending_colargs[0], Column) and \ - self._pending_colargs[0].table is not None: + isinstance(self._pending_colargs[0].table, Table): self._set_parent_with_dispatch(self._pending_colargs[0].table) def _set_parent(self, table): @@ -2121,7 +2120,7 @@ class CheckConstraint(Constraint): elif _autoattach: cols = sqlutil.find_columns(self.sqltext) tables = set([c.table for c in cols - if c.table is not None]) + if isinstance(c.table, Table)]) if len(tables) == 1: self._set_parent_with_dispatch( tables.pop()) diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 59e46de12..5a3a92a3e 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -423,7 +423,7 @@ class SQLCompiler(engine.Compiled): name = orig_name = column.name if name is None: raise exc.CompileError("Cannot compile Column object until " - "it's 'name' is assigned.") + "its 'name' is assigned.") is_literal = column.is_literal if not is_literal and isinstance(name, sql._truncated_label): @@ -787,14 +787,14 @@ class SQLCompiler(engine.Compiled): existing = self.binds[name] if existing is not bindparam: if (existing.unique or bindparam.unique) and \ - not existing.proxy_set.intersection(bindparam.proxy_set): + not existing.proxy_set.intersection( + bindparam.proxy_set): raise exc.CompileError( "Bind parameter '%s' conflicts with " "unique bind parameter of the same name" % bindparam.key ) - elif getattr(existing, '_is_crud', False) or \ - getattr(bindparam, '_is_crud', False): + elif existing._is_crud or bindparam._is_crud: raise exc.CompileError( "bindparam() name '%s' is reserved " "for automatic usage in the VALUES or SET " @@ -1086,14 +1086,9 @@ class SQLCompiler(engine.Compiled): positional_names=None, **kwargs): entry = self.stack and self.stack[-1] or {} - if not asfrom: - existingfroms = entry.get('from', None) - else: - # don't render correlations if we're rendering a FROM list - # entry - existingfroms = [] + existingfroms = entry.get('from', None) - froms = select._get_display_froms(existingfroms) + froms = select._get_display_froms(existingfroms, asfrom=asfrom) correlate_froms = set(sql._from_objects(*froms)) diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 490004e39..5cef778bb 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -181,10 +181,10 @@ def select(columns=None, whereclause=None, from_obj=[], **kwargs): string arguments, which will be converted as appropriate into either :func:`text()` or :func:`literal_column()` constructs. - See also: + .. seealso:: - :ref:`coretutorial_selecting` - Core Tutorial description of - :func:`.select`. + :ref:`coretutorial_selecting` - Core Tutorial description of + :func:`.select`. :param columns: A list of :class:`.ClauseElement` objects, typically @@ -464,7 +464,7 @@ def update(table, whereclause=None, values=None, inline=False, **kwargs): as_scalar() ) - See also: + .. seealso:: :ref:`inserts_and_updates` - SQL Expression Language Tutorial @@ -493,7 +493,7 @@ def delete(table, whereclause=None, **kwargs): condition of the ``UPDATE`` statement. Note that the :meth:`~Delete.where()` generative method may be used instead. - See also: + .. seealso:: :ref:`deletes` - SQL Expression Tutorial @@ -2873,6 +2873,8 @@ class BindParameter(ColumnElement): __visit_name__ = 'bindparam' quote = None + _is_crud = False + def __init__(self, key, value, type_=None, unique=False, callable_=None, isoutparam=False, required=False, @@ -3073,7 +3075,7 @@ class Executable(Generative): See :meth:`.Connection.execution_options` for a full list of possible options. - See also: + .. seealso:: :meth:`.Connection.execution_options()` @@ -3444,15 +3446,15 @@ class Case(ColumnElement): class FunctionElement(Executable, ColumnElement, FromClause): """Base for SQL function-oriented constructs. - See also: + .. seealso:: - :class:`.Function` - named SQL function. + :class:`.Function` - named SQL function. - :data:`.func` - namespace which produces registered or ad-hoc - :class:`.Function` instances. + :data:`.func` - namespace which produces registered or ad-hoc + :class:`.Function` instances. - :class:`.GenericFunction` - allows creation of registered function - types. + :class:`.GenericFunction` - allows creation of registered function + types. """ @@ -3571,15 +3573,13 @@ class Function(FunctionElement): See the superclass :class:`.FunctionElement` for a description of public methods. - See also: - - See also: + .. seealso:: - :data:`.func` - namespace which produces registered or ad-hoc - :class:`.Function` instances. + :data:`.func` - namespace which produces registered or ad-hoc + :class:`.Function` instances. - :class:`.GenericFunction` - allows creation of registered function - types. + :class:`.GenericFunction` - allows creation of registered function + types. """ @@ -4725,7 +4725,9 @@ class SelectBase(Executable, FromClause): """return a 'scalar' representation of this selectable, embedded as a subquery with a label. - See also :meth:`~.SelectBase.as_scalar`. + .. seealso:: + + :meth:`~.SelectBase.as_scalar`. """ return self.as_scalar().label(name) @@ -4843,9 +4845,9 @@ class SelectBase(Executable, FromClause): result = conn.execute(statement).fetchall() - See also: + .. seealso:: - :meth:`.orm.query.Query.cte` - ORM version of :meth:`.SelectBase.cte`. + :meth:`.orm.query.Query.cte` - ORM version of :meth:`.SelectBase.cte`. """ return CTE(self, name=name, recursive=recursive) @@ -4914,6 +4916,10 @@ class SelectBase(Executable, FromClause): The criterion will be appended to any pre-existing ORDER BY criterion. + This is an **in-place** mutation method; the + :meth:`~.SelectBase.order_by` method is preferred, as it provides standard + :term:`method chaining`. + """ if len(clauses) == 1 and clauses[0] is None: self._order_by_clause = ClauseList() @@ -4927,6 +4933,10 @@ class SelectBase(Executable, FromClause): The criterion will be appended to any pre-existing GROUP BY criterion. + This is an **in-place** mutation method; the + :meth:`~.SelectBase.group_by` method is preferred, as it provides standard + :term:`method chaining`. + """ if len(clauses) == 1 and clauses[0] is None: self._group_by_clause = ClauseList() @@ -4980,7 +4990,7 @@ class CompoundSelect(SelectBase): INTERSECT_ALL = util.symbol('INTERSECT ALL') def __init__(self, keyword, *selects, **kwargs): - self._should_correlate = kwargs.pop('correlate', False) + self._auto_correlate = kwargs.pop('correlate', False) self.keyword = keyword self.selects = [] @@ -5120,13 +5130,13 @@ class HasPrefixes(object): class Select(HasPrefixes, SelectBase): """Represents a ``SELECT`` statement. - See also: + .. seealso:: - :func:`~.expression.select` - the function which creates - a :class:`.Select` object. + :func:`~.expression.select` - the function which creates + a :class:`.Select` object. - :ref:`coretutorial_selecting` - Core Tutorial description - of :func:`.select`. + :ref:`coretutorial_selecting` - Core Tutorial description + of :func:`.select`. """ @@ -5159,7 +5169,7 @@ class Select(HasPrefixes, SelectBase): :class:`SelectBase` superclass. """ - self._should_correlate = correlate + self._auto_correlate = correlate if distinct is not False: if distinct is True: self._distinct = True @@ -5232,7 +5242,7 @@ class Select(HasPrefixes, SelectBase): return froms - def _get_display_froms(self, existing_froms=None): + def _get_display_froms(self, existing_froms=None, asfrom=False): """Return the full list of 'from' clauses to be displayed. Takes into account a set of existing froms which may be @@ -5258,18 +5268,29 @@ class Select(HasPrefixes, SelectBase): # using a list to maintain ordering froms = [f for f in froms if f not in toremove] - if len(froms) > 1 or self._correlate or self._correlate_except: + if not asfrom: if self._correlate: - froms = [f for f in froms if f not in - _cloned_intersection(froms, - self._correlate)] + froms = [ + f for f in froms if f not in + _cloned_intersection( + _cloned_intersection(froms, existing_froms or ()), + self._correlate + ) + ] if self._correlate_except: - froms = [f for f in froms if f in _cloned_intersection(froms, - self._correlate_except)] - if self._should_correlate and existing_froms: - froms = [f for f in froms if f not in - _cloned_intersection(froms, - existing_froms)] + froms = [ + f for f in froms if f in + _cloned_intersection( + froms, + self._correlate_except + ) + ] + + if self._auto_correlate and existing_froms and len(froms) > 1: + froms = [ + f for f in froms if f not in + _cloned_intersection(froms, existing_froms) + ] if not len(froms): raise exc.InvalidRequestError("Select statement '%s" @@ -5642,7 +5663,7 @@ class Select(HasPrefixes, SelectBase): :ref:`correlated_subqueries` """ - self._should_correlate = False + self._auto_correlate = False if fromclauses and fromclauses[0] is None: self._correlate = () else: @@ -5662,7 +5683,7 @@ class Select(HasPrefixes, SelectBase): :ref:`correlated_subqueries` """ - self._should_correlate = False + self._auto_correlate = False if fromclauses and fromclauses[0] is None: self._correlate_except = () else: @@ -5671,9 +5692,15 @@ class Select(HasPrefixes, SelectBase): def append_correlation(self, fromclause): """append the given correlation expression to this select() - construct.""" + construct. + + This is an **in-place** mutation method; the + :meth:`~.Select.correlate` method is preferred, as it provides standard + :term:`method chaining`. + + """ - self._should_correlate = False + self._auto_correlate = False self._correlate = set(self._correlate).union( _interpret_as_from(f) for f in fromclause) @@ -5681,6 +5708,10 @@ class Select(HasPrefixes, SelectBase): """append the given column expression to the columns clause of this select() construct. + This is an **in-place** mutation method; the + :meth:`~.Select.column` method is preferred, as it provides standard + :term:`method chaining`. + """ self._reset_exported() column = _interpret_as_column_or_from(column) @@ -5694,6 +5725,10 @@ class Select(HasPrefixes, SelectBase): """append the given columns clause prefix expression to this select() construct. + This is an **in-place** mutation method; the + :meth:`~.Select.prefix_with` method is preferred, as it provides standard + :term:`method chaining`. + """ clause = _literal_as_text(clause) self._prefixes = self._prefixes + (clause,) @@ -5704,6 +5739,10 @@ class Select(HasPrefixes, SelectBase): The expression will be joined to existing WHERE criterion via AND. + This is an **in-place** mutation method; the + :meth:`~.Select.where` method is preferred, as it provides standard + :term:`method chaining`. + """ self._reset_exported() whereclause = _literal_as_text(whereclause) @@ -5719,6 +5758,10 @@ class Select(HasPrefixes, SelectBase): The expression will be joined to existing HAVING criterion via AND. + This is an **in-place** mutation method; the + :meth:`~.Select.having` method is preferred, as it provides standard + :term:`method chaining`. + """ if self._having is not None: self._having = and_(self._having, _literal_as_text(having)) @@ -5729,6 +5772,10 @@ class Select(HasPrefixes, SelectBase): """append the given FromClause expression to this select() construct's FROM clause. + This is an **in-place** mutation method; the + :meth:`~.Select.select_from` method is preferred, as it provides standard + :term:`method chaining`. + """ self._reset_exported() fromclause = _interpret_as_from(fromclause) @@ -6124,9 +6171,9 @@ class Insert(ValuesBase): The :class:`.Insert` object is created using the :func:`~.expression.insert()` function. - See also: + .. seealso:: - :ref:`coretutorial_insert_expressions` + :ref:`coretutorial_insert_expressions` """ __visit_name__ = 'insert' diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index fd138cfec..520c90f99 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -13,12 +13,14 @@ from collections import deque """Utility functions that build upon SQL and Schema constructs.""" -def sort_tables(tables, skip_fn=None): +def sort_tables(tables, skip_fn=None, extra_dependencies=None): """sort a collection of Table objects in order of their foreign-key dependency.""" tables = list(tables) tuples = [] + if extra_dependencies is not None: + tuples.extend(extra_dependencies) def visit_foreign_key(fkey): if fkey.use_alter: @@ -507,6 +509,9 @@ class AnnotatedColumnElement(Annotated): """pull 'key' from parent, if not present""" return self._Annotated__element.key + @util.memoized_property + def info(self): + return self._Annotated__element.info # hard-generate Annotated subclasses. this technique # is used instead of on-the-fly types (i.e. type.__new__()) diff --git a/lib/sqlalchemy/testing/assertsql.py b/lib/sqlalchemy/testing/assertsql.py index 864ce5b4d..0e250f356 100644 --- a/lib/sqlalchemy/testing/assertsql.py +++ b/lib/sqlalchemy/testing/assertsql.py @@ -174,6 +174,8 @@ class CompiledSQL(SQLMatchRule): params = self.params if not isinstance(params, list): params = [params] + else: + params = list(params) all_params = list(params) all_received = list(_received_parameters) while params: diff --git a/lib/sqlalchemy/testing/plugin/noseplugin.py b/lib/sqlalchemy/testing/plugin/noseplugin.py index 4ce76363e..5bd7ff3cd 100644 --- a/lib/sqlalchemy/testing/plugin/noseplugin.py +++ b/lib/sqlalchemy/testing/plugin/noseplugin.py @@ -215,11 +215,8 @@ def _set_table_options(options, file_config): @post def _reverse_topological(options, file_config): if options.reversetop: - from sqlalchemy.orm import unitofwork, session, mapper, dependency - from sqlalchemy.util import topological - from sqlalchemy.testing.util import RandomSet - topological.set = unitofwork.set = session.set = mapper.set = \ - dependency.set = RandomSet + from sqlalchemy.orm.util import randomize_unitofwork + randomize_unitofwork() def _requirements_opt(options, opt_str, value, parser): @@ -361,7 +358,6 @@ class NoseSQLAlchemy(Plugin): The class being examined by the selector """ - if not issubclass(cls, fixtures.TestBase): return False elif cls.__name__.startswith('_'): diff --git a/lib/sqlalchemy/testing/profiling.py b/lib/sqlalchemy/testing/profiling.py index ae9d176b7..19a9731be 100644 --- a/lib/sqlalchemy/testing/profiling.py +++ b/lib/sqlalchemy/testing/profiling.py @@ -14,11 +14,12 @@ import pstats import time import collections from .. import util + try: import cProfile except ImportError: cProfile = None -from ..util.compat import jython, pypy, win32 +from ..util import jython, pypy, win32, update_wrapper _current_test = None @@ -210,7 +211,6 @@ class ProfileStatsFile(object): profile_f.write("%s %s %s\n" % (test_key, platform_key, c)) profile_f.close() -from sqlalchemy.util.compat import update_wrapper def function_call_count(variance=0.05): diff --git a/lib/sqlalchemy/testing/runner.py b/lib/sqlalchemy/testing/runner.py index 6ec73d7c8..2bdbaebd1 100644 --- a/lib/sqlalchemy/testing/runner.py +++ b/lib/sqlalchemy/testing/runner.py @@ -31,3 +31,13 @@ import nose def main(): nose.main(addplugins=[NoseSQLAlchemy()]) + +def setup_py_test(): + """Runner to use for the 'test_suite' entry of your setup.py. + + Prevents any name clash shenanigans from the command line + argument "test" that the "setup.py test" command sends + to nose. + + """ + nose.main(addplugins=[NoseSQLAlchemy()], argv=['runner']) diff --git a/lib/sqlalchemy/testing/schema.py b/lib/sqlalchemy/testing/schema.py index ad233ec22..325d74f1e 100644 --- a/lib/sqlalchemy/testing/schema.py +++ b/lib/sqlalchemy/testing/schema.py @@ -66,18 +66,27 @@ def Column(*args, **kw): col = schema.Column(*args, **kw) if 'test_needs_autoincrement' in test_opts and \ - kw.get('primary_key', False) and \ - exclusions.against('firebird', 'oracle'): - def add_seq(c, tbl): - c._init_items( - schema.Sequence(_truncate_name( - config.db.dialect, tbl.name + '_' + c.name + '_seq'), - optional=True) - ) - event.listen(col, 'after_parent_attach', add_seq, propagate=True) + kw.get('primary_key', False): + + # allow any test suite to pick up on this + col.info['test_needs_autoincrement'] = True + + # hardcoded rule for firebird, oracle; this should + # be moved out + if exclusions.against('firebird', 'oracle'): + def add_seq(c, tbl): + c._init_items( + schema.Sequence(_truncate_name( + config.db.dialect, tbl.name + '_' + c.name + '_seq'), + optional=True) + ) + event.listen(col, 'after_parent_attach', add_seq, propagate=True) return col + + + def _truncate_name(dialect, name): if len(name) > dialect.max_identifier_length: return name[0:max(dialect.max_identifier_length - 6, 0)] + \ diff --git a/lib/sqlalchemy/testing/suite/test_insert.py b/lib/sqlalchemy/testing/suite/test_insert.py index 66aa1ecfa..a00fde312 100644 --- a/lib/sqlalchemy/testing/suite/test_insert.py +++ b/lib/sqlalchemy/testing/suite/test_insert.py @@ -33,7 +33,7 @@ class LastrowidTest(fixtures.TablesTest): row = conn.execute(table.select()).first() eq_( row, - (1, "some data") + (config.db.dialect.default_sequence_base, "some data") ) def test_autoincrement_on_insert(self): @@ -132,7 +132,7 @@ class ReturningTest(fixtures.TablesTest): row = conn.execute(table.select()).first() eq_( row, - (1, "some data") + (config.db.dialect.default_sequence_base, "some data") ) @classmethod diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py index 5beed6aad..7cae48572 100644 --- a/lib/sqlalchemy/testing/suite/test_reflection.py +++ b/lib/sqlalchemy/testing/suite/test_reflection.py @@ -87,8 +87,10 @@ class ComponentReflectionTest(fixtures.TablesTest): test_needs_fk=True, ) - cls.define_index(metadata, users) - cls.define_views(metadata, schema) + if testing.requires.index_reflection.enabled: + cls.define_index(metadata, users) + if testing.requires.view_reflection.enabled: + cls.define_views(metadata, schema) @classmethod def define_index(cls, metadata, users): @@ -121,12 +123,14 @@ class ComponentReflectionTest(fixtures.TablesTest): self.assert_('test_schema' in insp.get_schema_names()) + @testing.requires.schema_reflection def test_dialect_initialize(self): engine = engines.testing_engine() assert not hasattr(engine.dialect, 'default_schema_name') inspect(engine) assert hasattr(engine.dialect, 'default_schema_name') + @testing.requires.schema_reflection def test_get_default_schema_name(self): insp = inspect(testing.db) eq_(insp.default_schema_name, testing.db.dialect.default_schema_name) @@ -157,6 +161,7 @@ class ComponentReflectionTest(fixtures.TablesTest): self._test_get_table_names() @testing.requires.table_reflection + @testing.requires.foreign_key_constraint_reflection def test_get_table_names_fks(self): self._test_get_table_names(order_by='foreign_key') @@ -261,6 +266,7 @@ class ComponentReflectionTest(fixtures.TablesTest): self._test_get_pk_constraint() @testing.requires.table_reflection + @testing.requires.primary_key_constraint_reflection @testing.requires.schemas def test_get_pk_constraint_with_schema(self): self._test_get_pk_constraint(schema='test_schema') diff --git a/lib/sqlalchemy/testing/util.py b/lib/sqlalchemy/testing/util.py index 2592c341e..d9ff14eaf 100644 --- a/lib/sqlalchemy/testing/util.py +++ b/lib/sqlalchemy/testing/util.py @@ -1,6 +1,5 @@ from ..util import jython, pypy, defaultdict, decorator -from ..util.compat import decimal - +import decimal import gc import time import random diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index b9f7b9444..08aba4b56 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -25,10 +25,10 @@ import codecs from . import exc, schema, util, processors, events, event from .sql import operators -from .sql.expression import _DefaultColumnComparator, column, bindparam +from .sql.expression import _DefaultColumnComparator from .util import pickle -from .util.compat import decimal from .sql.visitors import Visitable +import decimal default = util.importlater("sqlalchemy.engine", "default") NoneType = type(None) @@ -1372,8 +1372,7 @@ class Numeric(_DateAffinity, TypeEngine): implementations however, most of which contain an import for plain ``decimal`` in their source code, even though some such as psycopg2 provide hooks for alternate adapters. SQLAlchemy imports ``decimal`` - globally as well. While the alternate ``Decimal`` class can be patched - into SQLA's ``decimal`` module, overall the most straightforward and + globally as well. The most straightforward and foolproof way to use "cdecimal" given current DBAPI and Python support is to patch it directly into sys.modules before anything else is imported:: diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py index 249c46ead..57bbdca85 100644 --- a/lib/sqlalchemy/util/__init__.py +++ b/lib/sqlalchemy/util/__init__.py @@ -4,10 +4,9 @@ # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -from .compat import callable, cmp, reduce, defaultdict, py25_dict, \ +from .compat import callable, cmp, reduce, \ threading, py3k, py3k_warning, jython, pypy, cpython, win32, set_types, \ - buffer, pickle, update_wrapper, partial, md5_hex, decode_slice, \ - dottedgetter, parse_qsl, any, contextmanager, namedtuple, next, WeakSet + pickle, dottedgetter, parse_qsl, namedtuple, next, WeakSet from ._collections import KeyedTuple, ImmutableContainer, immutabledict, \ Properties, OrderedProperties, ImmutableProperties, OrderedDict, \ @@ -21,8 +20,8 @@ from .langhelpers import iterate_attributes, class_hierarchy, \ portable_instancemethod, unbound_method_to_callable, \ getargspec_init, format_argspec_init, format_argspec_plus, \ get_func_kwargs, get_cls_kwargs, decorator, as_interface, \ - memoized_property, memoized_instancemethod, \ - group_expirable_memoized_property, importlater, \ + memoized_property, memoized_instancemethod, md5_hex, \ + group_expirable_memoized_property, importlater, decode_slice, \ monkeypatch_proxied_specials, asbool, bool_or_str, coerce_kw_type,\ duck_type_collection, assert_arg_type, symbol, dictlike_iteritems,\ classproperty, set_creation_order, warn_exception, warn, NoneType,\ @@ -31,3 +30,10 @@ from .langhelpers import iterate_attributes, class_hierarchy, \ from .deprecations import warn_deprecated, warn_pending_deprecation, \ deprecated, pending_deprecation + +# things that used to be not always available, +# but are now as of current support Python versions +from collections import defaultdict +from functools import partial +from functools import update_wrapper +from contextlib import contextmanager diff --git a/lib/sqlalchemy/util/_collections.py b/lib/sqlalchemy/util/_collections.py index ca77103b2..2c9c982fb 100644 --- a/lib/sqlalchemy/util/_collections.py +++ b/lib/sqlalchemy/util/_collections.py @@ -6,7 +6,6 @@ """Collection classes and helpers.""" -import sys import itertools import weakref import operator @@ -649,37 +648,25 @@ class OrderedIdentitySet(IdentitySet): self.add(o) -if sys.version_info >= (2, 5): - class PopulateDict(dict): - """A dict which populates missing values via a creation function. +class PopulateDict(dict): + """A dict which populates missing values via a creation function. - Note the creation function takes a key, unlike - collections.defaultdict. + Note the creation function takes a key, unlike + collections.defaultdict. - """ - - def __init__(self, creator): - self.creator = creator - - def __missing__(self, key): - self[key] = val = self.creator(key) - return val -else: - class PopulateDict(dict): - """A dict which populates missing values via a creation function.""" + """ - def __init__(self, creator): - self.creator = creator + def __init__(self, creator): + self.creator = creator - def __getitem__(self, key): - try: - return dict.__getitem__(self, key) - except KeyError: - self[key] = value = self.creator(key) - return value + def __missing__(self, key): + self[key] = val = self.creator(key) + return val -# define collections that are capable of storing +# Define collections that are capable of storing # ColumnElement objects as hashable keys/elements. +# At this point, these are mostly historical, things +# used to be more complicated. column_set = set column_dict = dict ordered_column_set = OrderedSet diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py index 3725a8491..2a0f06f8e 100644 --- a/lib/sqlalchemy/util/compat.py +++ b/lib/sqlalchemy/util/compat.py @@ -54,44 +54,6 @@ else: except ImportError: import pickle - -# a controversial feature, required by MySQLdb currently -def buffer(x): - return x - -# Py2K -buffer = buffer -# end Py2K - -try: - from contextlib import contextmanager -except ImportError: - def contextmanager(fn): - return fn - -try: - from functools import update_wrapper -except ImportError: - def update_wrapper(wrapper, wrapped, - assigned=('__doc__', '__module__', '__name__'), - updated=('__dict__',)): - for attr in assigned: - setattr(wrapper, attr, getattr(wrapped, attr)) - for attr in updated: - getattr(wrapper, attr).update(getattr(wrapped, attr, ())) - return wrapper - -try: - from functools import partial -except ImportError: - def partial(func, *args, **keywords): - def newfunc(*fargs, **fkeywords): - newkeywords = keywords.copy() - newkeywords.update(fkeywords) - return func(*(args + fargs), **newkeywords) - return newfunc - - if sys.version_info < (2, 6): # emits a nasty deprecation warning # in newer pythons @@ -132,52 +94,6 @@ except ImportError: return tuptype try: - from collections import defaultdict -except ImportError: - class defaultdict(dict): - - def __init__(self, default_factory=None, *a, **kw): - if (default_factory is not None and - not hasattr(default_factory, '__call__')): - raise TypeError('first argument must be callable') - dict.__init__(self, *a, **kw) - self.default_factory = default_factory - - def __getitem__(self, key): - try: - return dict.__getitem__(self, key) - except KeyError: - return self.__missing__(key) - - def __missing__(self, key): - if self.default_factory is None: - raise KeyError(key) - self[key] = value = self.default_factory() - return value - - def __reduce__(self): - if self.default_factory is None: - args = tuple() - else: - args = self.default_factory, - return type(self), args, None, None, self.iteritems() - - def copy(self): - return self.__copy__() - - def __copy__(self): - return type(self)(self.default_factory, self) - - def __deepcopy__(self, memo): - import copy - return type(self)(self.default_factory, - copy.deepcopy(self.items())) - - def __repr__(self): - return 'defaultdict(%s, %s)' % (self.default_factory, - dict.__repr__(self)) - -try: from weakref import WeakSet except: import weakref @@ -199,79 +115,12 @@ except: def add(self, other): self._storage[other] = True - -# find or create a dict implementation that supports __missing__ -class _probe(dict): - def __missing__(self, key): - return 1 - -try: - try: - _probe()['missing'] - py25_dict = dict - except KeyError: - class py25_dict(dict): - def __getitem__(self, key): - try: - return dict.__getitem__(self, key) - except KeyError: - try: - missing = self.__missing__ - except AttributeError: - raise KeyError(key) - else: - return missing(key) -finally: - del _probe - - -try: - import hashlib - _md5 = hashlib.md5 -except ImportError: - import md5 - _md5 = md5.new - - -def md5_hex(x): - # Py3K - #x = x.encode('utf-8') - m = _md5() - m.update(x) - return m.hexdigest() - import time if win32 or jython: time_func = time.clock else: time_func = time.time -if sys.version_info >= (2, 5): - any = any -else: - def any(iterator): - for item in iterator: - if bool(item): - return True - else: - return False - -if sys.version_info >= (2, 5): - def decode_slice(slc): - """decode a slice object as sent to __getitem__. - - takes into account the 2.5 __index__() method, basically. - - """ - ret = [] - for x in slc.start, slc.stop, slc.step: - if hasattr(x, '__index__'): - x = x.__index__() - ret.append(x) - return tuple(ret) -else: - def decode_slice(slc): - return (slc.start, slc.stop, slc.step) if sys.version_info >= (2, 6): from operator import attrgetter as dottedgetter @@ -283,5 +132,11 @@ else: return obj return g +# Adapted from six.py +if py3k: + def b(s): + return s.encode("latin-1") +else: + def b(s): + return s -import decimal diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index a9b791234..e3aed24d8 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -15,10 +15,31 @@ import re import sys import types import warnings -from .compat import update_wrapper, set_types, threading, \ +from .compat import set_types, threading, \ callable, inspect_getfullargspec +from functools import update_wrapper from .. import exc +import hashlib +def md5_hex(x): + # Py3K + #x = x.encode('utf-8') + m = hashlib.md5() + m.update(x) + return m.hexdigest() + +def decode_slice(slc): + """decode a slice object as sent to __getitem__. + + takes into account the 2.5 __index__() method, basically. + + """ + ret = [] + for x in slc.start, slc.stop, slc.step: + if hasattr(x, '__index__'): + x = x.__index__() + ret.append(x) + return tuple(ret) def _unique_symbols(used, *bases): used = set(used) @@ -123,7 +144,7 @@ def get_cls_kwargs(cls): ctr = class_.__dict__.get('__init__', False) if (not ctr or not isinstance(ctr, types.FunctionType) or - not isinstance(ctr.func_code, types.CodeType)): + not isinstance(ctr.func_code, types.CodeType)): stack.update(class_.__bases__) continue @@ -256,7 +277,6 @@ def format_argspec_init(method, grouped=True): try: return format_argspec_plus(method, grouped=grouped) except TypeError: - self_arg = 'self' if method is object.__init__: args = grouped and '(self)' or 'self' else: @@ -784,7 +804,7 @@ def duck_type_collection(specimen, default=None): if hasattr(specimen, '__emulates__'): # canonicalize set vs sets.Set to a standard: the builtin set if (specimen.__emulates__ is not None and - issubclass(specimen.__emulates__, set_types)): + issubclass(specimen.__emulates__, set_types)): return set else: return specimen.__emulates__ diff --git a/lib/sqlalchemy/util/topological.py b/lib/sqlalchemy/util/topological.py index 6f895e7b7..de3dfd0ae 100644 --- a/lib/sqlalchemy/util/topological.py +++ b/lib/sqlalchemy/util/topological.py @@ -49,7 +49,8 @@ def sort(tuples, allitems): def find_cycles(tuples, allitems): - # straight from gvr with some mods + # adapted from: + # http://neopythonic.blogspot.com/2009/01/detecting-cycles-in-directed-graph.html edges = util.defaultdict(set) for parent, child in tuples: |
