Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/sql/base.py : 54%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# sql/base.py
2# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
8"""Foundational utilities common to many sql modules.
10"""
13import itertools
14import re
16from .visitors import ClauseVisitor
17from .. import exc
18from .. import util
21PARSE_AUTOCOMMIT = util.symbol("PARSE_AUTOCOMMIT")
22NO_ARG = util.symbol("NO_ARG")
25class Immutable(object):
26 """mark a ClauseElement as 'immutable' when expressions are cloned."""
28 def unique_params(self, *optionaldict, **kwargs):
29 raise NotImplementedError("Immutable objects do not support copying")
31 def params(self, *optionaldict, **kwargs):
32 raise NotImplementedError("Immutable objects do not support copying")
34 def _clone(self):
35 return self
38def _from_objects(*elements):
39 return itertools.chain(*[element._from_objects for element in elements])
42@util.decorator
43def _generative(fn, *args, **kw):
44 """Mark a method as generative."""
46 self = args[0]._generate()
47 fn(self, *args[1:], **kw)
48 return self
51class _DialectArgView(util.collections_abc.MutableMapping):
52 """A dictionary view of dialect-level arguments in the form
53 <dialectname>_<argument_name>.
55 """
57 def __init__(self, obj):
58 self.obj = obj
60 def _key(self, key):
61 try:
62 dialect, value_key = key.split("_", 1)
63 except ValueError as err:
64 util.raise_(KeyError(key), replace_context=err)
65 else:
66 return dialect, value_key
68 def __getitem__(self, key):
69 dialect, value_key = self._key(key)
71 try:
72 opt = self.obj.dialect_options[dialect]
73 except exc.NoSuchModuleError as err:
74 util.raise_(KeyError(key), replace_context=err)
75 else:
76 return opt[value_key]
78 def __setitem__(self, key, value):
79 try:
80 dialect, value_key = self._key(key)
81 except KeyError as err:
82 util.raise_(
83 exc.ArgumentError(
84 "Keys must be of the form <dialectname>_<argname>"
85 ),
86 replace_context=err,
87 )
88 else:
89 self.obj.dialect_options[dialect][value_key] = value
91 def __delitem__(self, key):
92 dialect, value_key = self._key(key)
93 del self.obj.dialect_options[dialect][value_key]
95 def __len__(self):
96 return sum(
97 len(args._non_defaults)
98 for args in self.obj.dialect_options.values()
99 )
101 def __iter__(self):
102 return (
103 util.safe_kwarg("%s_%s" % (dialect_name, value_name))
104 for dialect_name in self.obj.dialect_options
105 for value_name in self.obj.dialect_options[
106 dialect_name
107 ]._non_defaults
108 )
111class _DialectArgDict(util.collections_abc.MutableMapping):
112 """A dictionary view of dialect-level arguments for a specific
113 dialect.
115 Maintains a separate collection of user-specified arguments
116 and dialect-specified default arguments.
118 """
120 def __init__(self):
121 self._non_defaults = {}
122 self._defaults = {}
124 def __len__(self):
125 return len(set(self._non_defaults).union(self._defaults))
127 def __iter__(self):
128 return iter(set(self._non_defaults).union(self._defaults))
130 def __getitem__(self, key):
131 if key in self._non_defaults:
132 return self._non_defaults[key]
133 else:
134 return self._defaults[key]
136 def __setitem__(self, key, value):
137 self._non_defaults[key] = value
139 def __delitem__(self, key):
140 del self._non_defaults[key]
143class DialectKWArgs(object):
144 """Establish the ability for a class to have dialect-specific arguments
145 with defaults and constructor validation.
147 The :class:`.DialectKWArgs` interacts with the
148 :attr:`.DefaultDialect.construct_arguments` present on a dialect.
150 .. seealso::
152 :attr:`.DefaultDialect.construct_arguments`
154 """
156 @classmethod
157 def argument_for(cls, dialect_name, argument_name, default):
158 """Add a new kind of dialect-specific keyword argument for this class.
160 E.g.::
162 Index.argument_for("mydialect", "length", None)
164 some_index = Index('a', 'b', mydialect_length=5)
166 The :meth:`.DialectKWArgs.argument_for` method is a per-argument
167 way adding extra arguments to the
168 :attr:`.DefaultDialect.construct_arguments` dictionary. This
169 dictionary provides a list of argument names accepted by various
170 schema-level constructs on behalf of a dialect.
172 New dialects should typically specify this dictionary all at once as a
173 data member of the dialect class. The use case for ad-hoc addition of
174 argument names is typically for end-user code that is also using
175 a custom compilation scheme which consumes the additional arguments.
177 :param dialect_name: name of a dialect. The dialect must be
178 locatable, else a :class:`.NoSuchModuleError` is raised. The
179 dialect must also include an existing
180 :attr:`.DefaultDialect.construct_arguments` collection, indicating
181 that it participates in the keyword-argument validation and default
182 system, else :class:`.ArgumentError` is raised. If the dialect does
183 not include this collection, then any keyword argument can be
184 specified on behalf of this dialect already. All dialects packaged
185 within SQLAlchemy include this collection, however for third party
186 dialects, support may vary.
188 :param argument_name: name of the parameter.
190 :param default: default value of the parameter.
192 .. versionadded:: 0.9.4
194 """
196 construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name]
197 if construct_arg_dictionary is None:
198 raise exc.ArgumentError(
199 "Dialect '%s' does have keyword-argument "
200 "validation and defaults enabled configured" % dialect_name
201 )
202 if cls not in construct_arg_dictionary:
203 construct_arg_dictionary[cls] = {}
204 construct_arg_dictionary[cls][argument_name] = default
206 @util.memoized_property
207 def dialect_kwargs(self):
208 """A collection of keyword arguments specified as dialect-specific
209 options to this construct.
211 The arguments are present here in their original ``<dialect>_<kwarg>``
212 format. Only arguments that were actually passed are included;
213 unlike the :attr:`.DialectKWArgs.dialect_options` collection, which
214 contains all options known by this dialect including defaults.
216 The collection is also writable; keys are accepted of the
217 form ``<dialect>_<kwarg>`` where the value will be assembled
218 into the list of options.
220 .. versionadded:: 0.9.2
222 .. versionchanged:: 0.9.4 The :attr:`.DialectKWArgs.dialect_kwargs`
223 collection is now writable.
225 .. seealso::
227 :attr:`.DialectKWArgs.dialect_options` - nested dictionary form
229 """
230 return _DialectArgView(self)
232 @property
233 def kwargs(self):
234 """A synonym for :attr:`.DialectKWArgs.dialect_kwargs`."""
235 return self.dialect_kwargs
237 @util.dependencies("sqlalchemy.dialects")
238 def _kw_reg_for_dialect(dialects, dialect_name):
239 dialect_cls = dialects.registry.load(dialect_name)
240 if dialect_cls.construct_arguments is None:
241 return None
242 return dict(dialect_cls.construct_arguments)
244 _kw_registry = util.PopulateDict(_kw_reg_for_dialect)
246 def _kw_reg_for_dialect_cls(self, dialect_name):
247 construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name]
248 d = _DialectArgDict()
250 if construct_arg_dictionary is None:
251 d._defaults.update({"*": None})
252 else:
253 for cls in reversed(self.__class__.__mro__):
254 if cls in construct_arg_dictionary:
255 d._defaults.update(construct_arg_dictionary[cls])
256 return d
258 @util.memoized_property
259 def dialect_options(self):
260 """A collection of keyword arguments specified as dialect-specific
261 options to this construct.
263 This is a two-level nested registry, keyed to ``<dialect_name>``
264 and ``<argument_name>``. For example, the ``postgresql_where``
265 argument would be locatable as::
267 arg = my_object.dialect_options['postgresql']['where']
269 .. versionadded:: 0.9.2
271 .. seealso::
273 :attr:`.DialectKWArgs.dialect_kwargs` - flat dictionary form
275 """
277 return util.PopulateDict(
278 util.portable_instancemethod(self._kw_reg_for_dialect_cls)
279 )
281 def _validate_dialect_kwargs(self, kwargs):
282 # validate remaining kwargs that they all specify DB prefixes
284 if not kwargs:
285 return
287 for k in kwargs:
288 m = re.match("^(.+?)_(.+)$", k)
289 if not m:
290 raise TypeError(
291 "Additional arguments should be "
292 "named <dialectname>_<argument>, got '%s'" % k
293 )
294 dialect_name, arg_name = m.group(1, 2)
296 try:
297 construct_arg_dictionary = self.dialect_options[dialect_name]
298 except exc.NoSuchModuleError:
299 util.warn(
300 "Can't validate argument %r; can't "
301 "locate any SQLAlchemy dialect named %r"
302 % (k, dialect_name)
303 )
304 self.dialect_options[dialect_name] = d = _DialectArgDict()
305 d._defaults.update({"*": None})
306 d._non_defaults[arg_name] = kwargs[k]
307 else:
308 if (
309 "*" not in construct_arg_dictionary
310 and arg_name not in construct_arg_dictionary
311 ):
312 raise exc.ArgumentError(
313 "Argument %r is not accepted by "
314 "dialect %r on behalf of %r"
315 % (k, dialect_name, self.__class__)
316 )
317 else:
318 construct_arg_dictionary[arg_name] = kwargs[k]
321class Generative(object):
322 """Allow a ClauseElement to generate itself via the
323 @_generative decorator.
325 """
327 def _generate(self):
328 s = self.__class__.__new__(self.__class__)
329 s.__dict__ = self.__dict__.copy()
330 return s
333class Executable(Generative):
334 """Mark a ClauseElement as supporting execution.
336 :class:`.Executable` is a superclass for all "statement" types
337 of objects, including :func:`select`, :func:`delete`, :func:`update`,
338 :func:`insert`, :func:`text`.
340 """
342 supports_execution = True
343 _execution_options = util.immutabledict()
344 _bind = None
346 @_generative
347 def execution_options(self, **kw):
348 """ Set non-SQL options for the statement which take effect during
349 execution.
351 Execution options can be set on a per-statement or
352 per :class:`_engine.Connection` basis. Additionally, the
353 :class:`_engine.Engine` and ORM :class:`~.orm.query.Query`
354 objects provide
355 access to execution options which they in turn configure upon
356 connections.
358 The :meth:`execution_options` method is generative. A new
359 instance of this statement is returned that contains the options::
361 statement = select([table.c.x, table.c.y])
362 statement = statement.execution_options(autocommit=True)
364 Note that only a subset of possible execution options can be applied
365 to a statement - these include "autocommit" and "stream_results",
366 but not "isolation_level" or "compiled_cache".
367 See :meth:`_engine.Connection.execution_options` for a full list of
368 possible options.
370 .. seealso::
372 :meth:`_engine.Connection.execution_options`
374 :meth:`_query.Query.execution_options`
376 :meth:`.Executable.get_execution_options`
378 """
379 if "isolation_level" in kw:
380 raise exc.ArgumentError(
381 "'isolation_level' execution option may only be specified "
382 "on Connection.execution_options(), or "
383 "per-engine using the isolation_level "
384 "argument to create_engine()."
385 )
386 if "compiled_cache" in kw:
387 raise exc.ArgumentError(
388 "'compiled_cache' execution option may only be specified "
389 "on Connection.execution_options(), not per statement."
390 )
391 self._execution_options = self._execution_options.union(kw)
393 def get_execution_options(self):
394 """ Get the non-SQL options which will take effect during execution.
396 .. versionadded:: 1.3
398 .. seealso::
400 :meth:`.Executable.execution_options`
401 """
402 return self._execution_options
404 def execute(self, *multiparams, **params):
405 """Compile and execute this :class:`.Executable`.
407 """
408 e = self.bind
409 if e is None:
410 label = getattr(self, "description", self.__class__.__name__)
411 msg = (
412 "This %s is not directly bound to a Connection or Engine. "
413 "Use the .execute() method of a Connection or Engine "
414 "to execute this construct." % label
415 )
416 raise exc.UnboundExecutionError(msg)
417 return e._execute_clauseelement(self, multiparams, params)
419 def scalar(self, *multiparams, **params):
420 """Compile and execute this :class:`.Executable`, returning the
421 result's scalar representation.
423 """
424 return self.execute(*multiparams, **params).scalar()
426 @property
427 def bind(self):
428 """Returns the :class:`_engine.Engine` or :class:`_engine.Connection`
429 to
430 which this :class:`.Executable` is bound, or None if none found.
432 This is a traversal which checks locally, then
433 checks among the "from" clauses of associated objects
434 until a bound engine or connection is found.
436 """
437 if self._bind is not None:
438 return self._bind
440 for f in _from_objects(self):
441 if f is self:
442 continue
443 engine = f.bind
444 if engine is not None:
445 return engine
446 else:
447 return None
450class SchemaEventTarget(object):
451 """Base class for elements that are the targets of :class:`.DDLEvents`
452 events.
454 This includes :class:`.SchemaItem` as well as :class:`.SchemaType`.
456 """
458 def _set_parent(self, parent):
459 """Associate with this SchemaEvent's parent object."""
461 def _set_parent_with_dispatch(self, parent):
462 self.dispatch.before_parent_attach(self, parent)
463 self._set_parent(parent)
464 self.dispatch.after_parent_attach(self, parent)
467class SchemaVisitor(ClauseVisitor):
468 """Define the visiting for ``SchemaItem`` objects."""
470 __traverse_options__ = {"schema_visitor": True}
473class ColumnCollection(util.OrderedProperties):
474 """An ordered dictionary that stores a list of ColumnElement
475 instances.
477 Overrides the ``__eq__()`` method to produce SQL clauses between
478 sets of correlated columns.
480 """
482 __slots__ = "_all_columns"
484 def __init__(self, *columns):
485 super(ColumnCollection, self).__init__()
486 object.__setattr__(self, "_all_columns", [])
487 for c in columns:
488 self.add(c)
490 def __str__(self):
491 return repr([str(c) for c in self])
493 def replace(self, column):
494 """add the given column to this collection, removing unaliased
495 versions of this column as well as existing columns with the
496 same key.
498 e.g.::
500 t = Table('sometable', metadata, Column('col1', Integer))
501 t.columns.replace(Column('col1', Integer, key='columnone'))
503 will remove the original 'col1' from the collection, and add
504 the new column under the name 'columnname'.
506 Used by schema.Column to override columns during table reflection.
508 """
509 remove_col = None
510 if column.name in self and column.key != column.name:
511 other = self[column.name]
512 if other.name == other.key:
513 remove_col = other
514 del self._data[other.key]
516 if column.key in self._data:
517 remove_col = self._data[column.key]
519 self._data[column.key] = column
520 if remove_col is not None:
521 self._all_columns[:] = [
522 column if c is remove_col else c for c in self._all_columns
523 ]
524 else:
525 self._all_columns.append(column)
527 def add(self, column):
528 """Add a column to this collection.
530 The key attribute of the column will be used as the hash key
531 for this dictionary.
533 """
534 if not column.key:
535 raise exc.ArgumentError(
536 "Can't add unnamed column to column collection"
537 )
538 self[column.key] = column
540 def __delitem__(self, key):
541 raise NotImplementedError()
543 def __setattr__(self, key, obj):
544 raise NotImplementedError()
546 def __setitem__(self, key, value):
547 if key in self:
549 # this warning is primarily to catch select() statements
550 # which have conflicting column names in their exported
551 # columns collection
553 existing = self[key]
555 if existing is value:
556 return
558 if not existing.shares_lineage(value):
559 util.warn(
560 "Column %r on table %r being replaced by "
561 "%r, which has the same key. Consider "
562 "use_labels for select() statements."
563 % (key, getattr(existing, "table", None), value)
564 )
566 # pop out memoized proxy_set as this
567 # operation may very well be occurring
568 # in a _make_proxy operation
569 util.memoized_property.reset(value, "proxy_set")
571 self._all_columns.append(value)
572 self._data[key] = value
574 def clear(self):
575 raise NotImplementedError()
577 def remove(self, column):
578 del self._data[column.key]
579 self._all_columns[:] = [
580 c for c in self._all_columns if c is not column
581 ]
583 def update(self, iter_):
584 cols = list(iter_)
585 all_col_set = set(self._all_columns)
586 self._all_columns.extend(
587 c for label, c in cols if c not in all_col_set
588 )
589 self._data.update((label, c) for label, c in cols)
591 def extend(self, iter_):
592 cols = list(iter_)
593 all_col_set = set(self._all_columns)
594 self._all_columns.extend(c for c in cols if c not in all_col_set)
595 self._data.update((c.key, c) for c in cols)
597 __hash__ = None
599 @util.dependencies("sqlalchemy.sql.elements")
600 def __eq__(self, elements, other):
601 l = []
602 for c in getattr(other, "_all_columns", other):
603 for local in self._all_columns:
604 if c.shares_lineage(local):
605 l.append(c == local)
606 return elements.and_(*l)
608 def __contains__(self, other):
609 if not isinstance(other, util.string_types):
610 raise exc.ArgumentError("__contains__ requires a string argument")
611 return util.OrderedProperties.__contains__(self, other)
613 def __getstate__(self):
614 return {"_data": self._data, "_all_columns": self._all_columns}
616 def __setstate__(self, state):
617 object.__setattr__(self, "_data", state["_data"])
618 object.__setattr__(self, "_all_columns", state["_all_columns"])
620 def contains_column(self, col):
621 return col in set(self._all_columns)
623 def as_immutable(self):
624 return ImmutableColumnCollection(self._data, self._all_columns)
627class ImmutableColumnCollection(util.ImmutableProperties, ColumnCollection):
628 def __init__(self, data, all_columns):
629 util.ImmutableProperties.__init__(self, data)
630 object.__setattr__(self, "_all_columns", all_columns)
632 extend = remove = util.ImmutableProperties._immutable
635class ColumnSet(util.ordered_column_set):
636 def contains_column(self, col):
637 return col in self
639 def extend(self, cols):
640 for col in cols:
641 self.add(col)
643 def __add__(self, other):
644 return list(self) + list(other)
646 @util.dependencies("sqlalchemy.sql.elements")
647 def __eq__(self, elements, other):
648 l = []
649 for c in other:
650 for local in self:
651 if c.shares_lineage(local):
652 l.append(c == local)
653 return elements.and_(*l)
655 def __hash__(self):
656 return hash(tuple(x for x in self))
659def _bind_or_error(schemaitem, msg=None):
660 bind = schemaitem.bind
661 if not bind:
662 name = schemaitem.__class__.__name__
663 label = getattr(
664 schemaitem, "fullname", getattr(schemaitem, "name", None)
665 )
666 if label:
667 item = "%s object %r" % (name, label)
668 else:
669 item = "%s object" % name
670 if msg is None:
671 msg = (
672 "%s is not bound to an Engine or Connection. "
673 "Execution can not proceed without a database to execute "
674 "against." % item
675 )
676 raise exc.UnboundExecutionError(msg)
677 return bind