Opened 4 months ago
Last modified 2 months ago
#36415 assigned New feature
Add a public API to unregister model field lookups
Reported by: | Tim Graham | Owned by: | Tim Graham |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Accepted | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Motivated by the ongoing work to build a MongoDB backend, I'm interested in writing code like this:
EmbeddedModelField._unregister_lookup(GreaterThanOrEqual)
(a lot of the built-in lookups aren't applicable to EmbeddedModelField
... I think we only want exact
.)
As far as I can tell, Field._unregister_lookup()
isn't a public API âto concerns about thread-safety.
Simon Charette provided this guidance:
As far as I know we don't run tests in a multi-threaded environment (it's multi-process based) so I'm not sure what this comment is referring to.
If you implement this logic I suspect you'll need to use a notion of sentinel / tombstone to denote unregistration to work like you intend as GreaterThanOrEqual
is registered for Field
as well.
The following code might explain why the current situation is broken:
from django.db.models import Field from django.db.models.lookups import GreaterThanOrEqual class EmbeddedModelField(Field): ... >>> EmbeddedModelField._unregister_lookup(GreaterThanOrEqual) >>> assert "gte" not in EmbeddedModelField.get_lookups() >>> assert "gte" in Field.get_lookups()
I think that's what the "thread-safe" mention was referring to, it was more along the lines that _unregister_lookup()
is broken if it doesn't have a corresponding register_lookup()
call to restore its state because the unregistration logic doesn't ensure to create a per-class override prior to deletion.
A potential solution to this problem would be to avoid any form of deletion of registered lookups and use a UnregisteredLookup
sentinel (or simply None
) instead:
-
django/db/models/query_utils.py
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index f0ae810f47..4df1324825 100644
a b def register_instance_lookup(self, lookup, lookup_name=None): 337 337 register_class_lookup = classmethod(register_class_lookup) 338 338 339 339 def _unregister_class_lookup(cls, lookup, lookup_name=None): 340 """341 Remove given lookup from cls lookups. For use in tests only as it's342 not thread-safe.343 """344 340 if lookup_name is None: 345 341 lookup_name = lookup.lookup_name 346 del cls.class_lookups[lookup_name] 342 if "class_lookups" not in cls.__dict__: 343 cls.class_lookups = {} 344 cls.class_lookups[lookup_name] = None 347 345 cls._clear_cached_class_lookups() 348 346 349 347 def _unregister_instance_lookup(self, lookup, lookup_name=None): âĻ âĻ def _unregister_instance_lookup(self, lookup, lookup_name=None): 353 351 """ 354 352 if lookup_name is None: 355 353 lookup_name = lookup.lookup_name 356 del self.instance_lookups[lookup_name] 354 if "instance_lookups" not in self.__dict__: 355 self.instance_lookups = {} 356 self.instance_lookups[lookup_name] = None 357 357 358 358 _unregister_lookup = class_or_instance_method( 359 359 _unregister_class_lookup, _unregister_instance_lookup
None is a suitable sentinel as both get_lookup
and get_transform
treat it as a missing entry.
Change History (3)
comment:1 by , 4 months ago
Triage Stage: | Unreviewed â Accepted |
---|
comment:3 by , 2 months ago
I'll deassign the ticket if I'm not going to return to it. It's not an easy task anyway.
Given there are few cases where âwe could use this internally and a registration interface already exists I think it's work adding as it's quite hard to get unregistration right now that we have class and instance level lookups support.