-
Notifications
You must be signed in to change notification settings - Fork 261
Implement column defaults for INSERT/UPDATE #206
base: master
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,6 +9,9 @@ | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| from sqlalchemy import text | ||||||||||||||||||||||||||
| from sqlalchemy.sql import ClauseElement | ||||||||||||||||||||||||||
| from sqlalchemy.sql.dml import ValuesBase | ||||||||||||||||||||||||||
| from sqlalchemy.sql.expression import type_coerce | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| from databases.importer import import_from_string | ||||||||||||||||||||||||||
| from databases.interfaces import ConnectionBackend, DatabaseBackend, TransactionBackend | ||||||||||||||||||||||||||
|
|
@@ -294,11 +297,51 @@ def _build_query( | |||||||||||||||||||||||||
| query = text(query) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return query.bindparams(**values) if values is not None else query | ||||||||||||||||||||||||||
| elif values: | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # 2 paths where we apply column defaults: | ||||||||||||||||||||||||||
| # - values are supplied (the object must be a ValuesBase) | ||||||||||||||||||||||||||
| # - values is None but the object is a ValuesBase | ||||||||||||||||||||||||||
| if values is not None and not isinstance(query, ValuesBase): | ||||||||||||||||||||||||||
| raise TypeError("values supplied but query doesn't support .values()") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if values is not None or isinstance(query, ValuesBase): | ||||||||||||||||||||||||||
| values = Connection._apply_column_defaults(query, values) | ||||||||||||||||||||||||||
|
Comment on lines
+299
to
+307
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
| return query.values(**values) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return query | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||||||
| def _apply_column_defaults(query: ValuesBase, values: dict = None) -> dict: | ||||||||||||||||||||||||||
| """Add default values from the table of a query.""" | ||||||||||||||||||||||||||
| new_values = {} | ||||||||||||||||||||||||||
| values = values or {} | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| for column in query.table.c: | ||||||||||||||||||||||||||
| if column.name in values: | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if column.default: | ||||||||||||||||||||||||||
| default = column.default | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if default.is_sequence: # pragma: no cover | ||||||||||||||||||||||||||
| # TODO: support sequences | ||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||
|
Comment on lines
+325
to
+327
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. assert not default.is_sequence, "sequences are not supported, PRs welcome" |
||||||||||||||||||||||||||
| elif default.is_callable: | ||||||||||||||||||||||||||
| value = default.arg(FakeExecutionContext()) | ||||||||||||||||||||||||||
| elif default.is_clause_element: # pragma: no cover | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||
| # TODO: implement clause element | ||||||||||||||||||||||||||
| # For this, the _build_query method needs to | ||||||||||||||||||||||||||
| # become an instance method so that it can access | ||||||||||||||||||||||||||
| # self._connection. | ||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| value = default.arg | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
| new_values[column.name] = value | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| new_values.update(values) | ||||||||||||||||||||||||||
| return new_values | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| class Transaction: | ||||||||||||||||||||||||||
| def __init__( | ||||||||||||||||||||||||||
|
|
@@ -489,3 +532,20 @@ def __repr__(self) -> str: | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def __eq__(self, other: typing.Any) -> bool: | ||||||||||||||||||||||||||
| return str(self) == str(other) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| class FakeExecutionContext: | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(it's not completely fake, after all) |
||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| This is an object that raises an error when one of its properties are | ||||||||||||||||||||||||||
| attempted to be accessed. Because we're not _really_ using SQLAlchemy | ||||||||||||||||||||||||||
| (besides using its query builder), we can't pass a real ExecutionContext | ||||||||||||||||||||||||||
| to ColumnDefault objects. This class makes it so that any attempts to | ||||||||||||||||||||||||||
| access the execution context argument by a column default callable | ||||||||||||||||||||||||||
| blows up loudly and clearly. | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def __getattr__(self, _: str) -> typing.NoReturn: # pragma: no cover | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should cover this by another test which tests raising NotImplementedError |
||||||||||||||||||||||||||
| raise NotImplementedError( | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw: my own custom implementation of this (yep, I have a similar hack in my prod), I pass the current |
||||||||||||||||||||||||||
| "Databases does not have a real SQLAlchemy ExecutionContext " | ||||||||||||||||||||||||||
| "implementation." | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.