-
Notifications
You must be signed in to change notification settings - Fork 264
Implement column defaults for INSERT/UPDATE #206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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 8000 a>.