-
Notifications
You must be signed in to change notification settings - Fork 67
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
fix: support polymodels with equal names but different roots (#961) #986
base: main
Are you sure you want to change the base?
Conversation
@classmethod | ||
def _update_kind_map(cls): | ||
"""Override; called by Model._fix_up_properties(). | ||
|
||
Update the kind map as well as the class map, except for PolyModel | ||
itself (its class key is empty). Note that the kind map will | ||
contain entries for all classes in a PolyModel hierarchy; they all | ||
have the same kind, but different class names. PolyModel class | ||
names, like regular Model class names, must be globally unique. | ||
have the same kind, but different class names. | ||
""" | ||
cls._kind_map[cls._class_name()] = cls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the reason why class names have to be globally unique, unfortunately. Different model classes will overwrite each other here if they have the same class name.
You can see this by adding the following to the end of your test below:
assert isinstance(CatCommand._kind_map["Cat"].bar, model.StringProperty)
assert not isinstance(CatAnimal._kind_map["Cat"].bar, model.StringProperty)
Notice how on the second line, even though we're accessing it through CatAnimal
, the model referenced by CatAnimal._kind_map["Cat"]
is for a CatCommand
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, they are overwritten, but there's a code above it that uses its own dictionary using fully qualified names to look up for the real entity during model instance creation. So, yes, non-unique polymorphic classes, when accessed using _kind_map
will be buggy, but why would a polymodel be looked up via global registry of top level classes? Polymodels provide their own registry and don't care what other registries think of them.
At least my legacy code with my fix doesn't experience any problems in my scenario as it did not in legacy ndb. And I use polymorphic models actively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like I can override _class_name
for PolyModel
to return something like Animal.Cat
and Command.Cat
. In this case even _kind_map
will keep its invariant.
In the new version of NDB a breaking requirement/implementation has been added requiring all the polymodel leaves to have unique names. There is no technical limitation for this requirement either on client or server side. So, by the introduction of this requirement we make it unable for the clients with legacy data to retrieve their models written by
appengine.ext.ndb
.Interestingly enough, currently there is an unused class property
PolyModel._class_map
which is populated by it but is never read. On the other side we havemodel._entity_from_ds_entity
that discards the rest of theclass_key
passed to it, taking just just the last element and looking it as if it were an ordinary model. These two shortcomings are as if calling for each other - an unused property and a client using incomplete data.This PR fixes this inconsistency and re-enables the ability to read and write polymodels with equal names if their class_keys are different. For this purpose the
_class_map
property is moved toModel
class so that it can read from it. I avoided introduction of a function for accessing it, because unlikelookup_kind
this function should not raise an exception if the model is not found. Anyhow, it is requested during the review, I will add a wrapper function along with its tests or perform any other changes more persistent to the design of the library.