#Is there an easy way to have Django let me know that I've set an "unknown" attribute on a model?
40 messages ยท Page 1 of 1 (latest)
Whether it is safe or efficient, or if/how it would work with a Django model, no idea... but off the top of my head:
>>> class Unsettable:
... x = None
... y = None
... def __setattr__(self, name, value):
... if not hasattr(self, name): raise KeyError
... super().__setattr__(name, value)
...
>>> u = Unsettable()
>>> u.x = 10
>>> u.z = 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __setattr__
KeyError
That with the _meta API might get you started on the right path...
https://docs.djangoproject.com/en/5.0/ref/models/meta/#django.db.models.options.Options.get_field
If t.y = "ho there" does call __setattr__ (and not some kind of Django magic), something like
def __setattr__(self, name, value):
try:
self._meta.get_field(name)
super().__setattr__(name, value)
except FieldDoesNotExist:
print("learn to type")
maybe ๐ค
Cooking now, but if you implement it, let me know ๐
ha, thanks. Might try this out later. (FWIW I would not push this to prod; it's just a development aid.)
Does django-stubs and static type analysis help with this?
I'd have to check that when I go back to writing Django, but it certainly wouldn't help in the REPL, where I presume this would be most useful (at least for me).
I do this:
def set_fields(obj: Model, **kwargs):
update_fields = []
for k, v in kwargs.items():
if getattr(obj, k, sentinel) != v:
setattr(obj, k, v)
update_fields.append(k)
return update_fields
def write_fields(obj: Model, **kwargs):
update_fields = set_fields(obj, **kwargs)
if update_fields:
try:
obj.save(update_fields=kwargs.keys())
except DatabaseError as e:
if str(e) != 'Save with update_fields did not affect any rows.':
raise
return True
return False
So instead of
x = Foo.objects.get(...)
x.bar = 1
x.save()
I do:
x = Foo.objects.get(...)
write_fields(x, bar=1)
This has stopped a lot of bugs very early
How does that stop you setting attributes on the instance that don't exist?
Instance? You meant attribute?
No? offby1's question was "I want a warning when I set an attribute on a model instance that doesn't exist/isn't one of the fields"
Yea, that attribute doesn't exist on the model. The code above stops that.
I interpret that question as "how do you stop setting attributes on (an instance that doesn't exist)?"
Which made me very confused :P
How? Maybe I'm just not reading the code well enough, but there's no point where you're checking the attributes on the instance?
Well that's the difference between the word "don't" and "doesn't" ๐
Seriousy though, I pass the fields in the save call
obj.save(update_fields=kwargs.keys())
Right, so it's on the model save rather than the setting of the attribute. I didn't realise that throws an exception when passed fields that don't exist, rather than ignoring them ๐
Yea exactly.
This is horrible DX though: ```python
x = Foo.objects.get(...)
write_fields(x, bar=1)
is it? It's less code than
x = Foo.objects.get(...)
x.bar = 1
x.save()
Sure, but less isn't always better?
Different doesn't mean "horrible DX". You'd have to be more specific why you don't like it imo.
I agree it's very different.
It's ugly (yes I realise that's subjective), it goes against standard Django conventions of "set attribute, call save", it goes against standard Python conventions of "how you set attributes on objects", etc
Yea, sure. But those conventions are the problem in the first place. ๐คทโโ๏ธ
The DX of silently ignoring errors.. now that is "horrible"
But yea, you could go the __setattr__ path, but I would say you'd need to whitelist setting stuff starting with _, and my thing above solves more problems than the one asked. It also avoid overwriting fields touched by other updates as it doesn't overwrite everything.
It also avoids the save altogether if no changes were made.
It might take a while to get used to, like most changes, but at a first glance it looks like a nice solution.
Even for me who wrote it, it took some getting used to for sure. 10+ years of Django habits was fighting me :)
One downside of mine is that it requires an abstract base model or something, which makes it more difficult to use in dev only... a separate function does help there.
I think the idea of running checks in dev only is a bad idea. It's in prod you really don't want silent errors that you never learn about.
The base class has the problem that you can't easily add it to third party library models.
ah but then you have to remember to call write_fields ๐
well anyway I have plenty of tests, so even if I do nothing, at worst I'll waste some time wondering why a test is failing (before eventually figuring out "oh I should have set obj.x and not obj.y"
You can make a test that bans .save() from the code base. I have a few custom checks like that.