Opened 10 days ago

Closed 10 days ago

Last modified 10 days ago

#35984 closed Bug (needsinfo)

functools.singledispatchmethod on Django models don't work

Reported by: Simon Gomizelj Owned by:
Component: Database layer (models, ORM) Version: 4.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I'm on Django 4.2.17 and Python 3.13.1

The following code works on Python 3.12 and I believe https://github.com/python/cpython/issues/85160 is the cause

When traversing a relationship and then calling a singledispatchmethod method registered on a model, the reference to self seems to get cached and locked to the first created model.

class Test(models.Model):
    ...

class Test2(models.Model):
    relationship = models.ForeignKey("Test", on_delete=models.CASCADE)

    @singledispatchmethod
    def bug(self, x):
        print(">", id(self))

    @bug.register
    def _(self, x: int):
        print(">", id(self))

and then it's pretty easy to trigger:

for t in test.test2_set.all():
    print("id:", id(t))  # always changes
    t.bug(4)             # only ever prints a single id

Change History (5)

comment:1 by Simon Gomizelj, 10 days ago

Version: 5.14.2

comment:2 by Tim Graham, 10 days ago

Component: UncategorizedDatabase layer (models, ORM)
Resolution: needsinfo
Status: newclosed
Type: UncategorizedBug

Why create a bug report here when you believe cpython is at fault? Feel free to reopen with more explanation if you believe Django is at fault and can explain what action we should take.

comment:3 by Simon Gomizelj, 10 days ago

Because honestly I'm not sure. I can't seem to replicate this behaviour outside of Django. Maybe Django is doing something non-standard. Either way, I have filed a cpython issue as well. We'll see where that goes.

comment:4 by Simon Gomizelj, 10 days ago

I know what the issue is. There's a weak key map under the hood:

class singledispatchmethod:
    # snip

    def __get__(self, obj, cls=None):
        if self._method_cache is not None:
            try:
                _method = self._method_cache[obj]
            except TypeError:
                self._method_cache = None
            except KeyError:
                pass
            else:
                return _method

Since Django models __hash__ is driven by the pk, this is fundamentally incompatible with this optimization.

So more specifically, this bug then only occurs when the primary key is the same.

Last edited 10 days ago by Simon Gomizelj (previous) (diff)

comment:5 by Simon Gomizelj, 10 days ago

Apologies - absolutely a cpython bug. Pulling at this thread I figured out how to replicate it with just the Python stdlib.

Note: See TracTickets for help on using tickets.
Back to Top