Since the resulting exception cannot be differentiated by its `type` from
any other exception raised during `save` (e.g. a failed `UPDATE` also
results in a `DatabaseError`) the only pattern to gracefully handle this
rare edge case to catch all `DatabaseError` and compare its `.args[0]` (or
`str` representation) to a string that doesn't offer any stability
guarantees.
In order to make it easier for advanced users that rely on the
`update_fields` feature to handle this case differently from others where
a `DatabaseError` is raised I propose that that we introduced a new
`DatabaseError` subclass that would be raised instead when an unexpected
empty update occurs.
I believe
[https://github.com/django/django/blob/3b3f38b3b09b0f2373e51406ecb8c9c45d36aebc/django/db/models/base.py#L997-L1000
both instances] should be switched to this pattern (`force_update` and
`update_fields`).
--
Ticket URL: <https://code.djangoproject.com/ticket/33579>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
Old description:
> When `Model.save(update_fields)` is used to update an instance of a
> previously fetched model and the resulting `UPDATE` doesn't affect any
> row a `DatabaseError`
> [https://github.com/django/django/blob/3b3f38b3b09b0f2373e51406ecb8c9c45d36aebc/django/db/models/base.py#L1000
> is raised].
>
> Since the resulting exception cannot be differentiated by its `type` from
> any other exception raised during `save` (e.g. a failed `UPDATE` also
> results in a `DatabaseError`) the only pattern to gracefully handle this
> rare edge case to catch all `DatabaseError` and compare its `.args[0]`
> (or `str` representation) to a string that doesn't offer any stability
> guarantees.
>
> In order to make it easier for advanced users that rely on the
> `update_fields` feature to handle this case differently from others where
> a `DatabaseError` is raised I propose that that we introduced a new
> `DatabaseError` subclass that would be raised instead when an unexpected
> empty update occurs.
>
> I believe
> [https://github.com/django/django/blob/3b3f38b3b09b0f2373e51406ecb8c9c45d36aebc/django/db/models/base.py#L997-L1000
> both instances] should be switched to this pattern (`force_update` and
> `update_fields`).
New description:
When `Model.save(update_fields)` is used to update an instance of a
previously fetched model and the resulting `UPDATE` doesn't affect any row
a `DatabaseError`
[https://github.com/django/django/blob/3b3f38b3b09b0f2373e51406ecb8c9c45d36aebc/django/db/models/base.py#L1000
is raised].
Since the resulting exception cannot be differentiated by its `type` from
any other dababase errors occuring during `save` (e.g. a failed `UPDATE`
also results in a `DatabaseError`) the only pattern to gracefully handle
this rare edge case to catch all `DatabaseError` and compare its
`.args[0]` (or `str` representation) to a string that doesn't offer any
stability guarantees.
In order to make it easier for advanced users that rely on the
`update_fields` feature to handle this case differently from others where
a `DatabaseError` is raised I propose that that we introduce a new
`DatabaseError` subclass that would be raised instead when an unexpected
empty update occurs.
I believe
[https://github.com/django/django/blob/3b3f38b3b09b0f2373e51406ecb8c9c45d36aebc/django/db/models/base.py#L997-L1000
both instances] should be switched to this pattern (`force_update` and
`update_fields`).
--
--
Ticket URL: <https://code.djangoproject.com/ticket/33579#comment:1>
* stage: Unreviewed => Accepted
--
Ticket URL: <https://code.djangoproject.com/ticket/33579#comment:2>
Comment (by Mohamed Nabil Rady):
Is it the best the idea to introduce a new subclass rather than using one
of the other ones ? Because I think we should stick with the ones
specified in [https://peps.python.org/pep-0249 PEP 249].
Not sure if we should raise a {{{ ProgrammingError }}} or {{{
InterfaceError }}}
--
Ticket URL: <https://code.djangoproject.com/ticket/33579#comment:3>
Comment (by Simon Charette):
I feel like a `django.db.models.Model.save` forced update failure has
little to do with PEP-249; it's an exception that occurs at the ORM layer
and not at the backend one.
> Not sure if we should raise a ProgrammingError or InterfaceError
I would argue that we should not subclass either of them as it will result
in the same effect as raising `DatabaseError` from a callers perspective
(no way to differentiate between a database level error and an ORM level
one without a `str(exc)` comparison).
The reason for proposing to subclass `DatabaseError` is solely for
backward compatibility purposes which I believed would make the proposed
change less controversial.
If it was do all over again I think we should have a dedicated
`NoopUpdate` (better name welcome) type like we do with `DoesNotExist` and
`MultipleObjectsReturned` but [https://bugs.python.org/issue12029 since
Python doesn't allow for virtual subclass overrides in except clauses]
there's no way to provide a deprecation path for `except DatabaseError`
clauses in the wild that might already be handling this case.
--
Ticket URL: <https://code.djangoproject.com/ticket/33579#comment:4>
* cc: Sardorbek Imomaliev (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/33579#comment:5>
* owner: nobody => Dulalet
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/33579#comment:6>
Comment (by Simon Charette):
Another alternative we would be to simply raise a `base.DoesNotExists`
error instead.
It would be backward incompatible in the sense that a different exception
type would be raised but it seems it would fit elegantly with the message
it's meant to convey; the object that was attempted to be updated was not
found. It would also make code that handles generic `DoesNotExist` and
turn them into HTTP 404 automatically pick up errors of this form without
any form of special casing.
Another argument for this approach is that the affected ''base'' is
already available from `Model._save_table` via the `cls` kwarg so it would
also allow to differentiate which model row was missing when dealing with
concrete model inheritance that require update against multiple tables.
--
Ticket URL: <https://code.djangoproject.com/ticket/33579#comment:7>