-
Notifications
You must be signed in to change notification settings - Fork 264
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
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: | ||||||||||||||||||||||||||
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
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 | ||||||||||||||||||||||||||
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
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 | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
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: | ||||||||||||||||||||||||||
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 | ||||||||||||||||||||||||||
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( | ||||||||||||||||||||||||||
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.