redis-om-python
redis-om-python copied to clipboard
PrimaryKeyCreator, no parameter are passed to the create_pk method
I want to make an MurmurHash class to create a primary key for a new model instance. I did this class that adheres to the PrimaryKeyCreator protocol:
# https://github.com/hajimes/mmh3
import mmh3
class Mmh3PrimaryKey:
"""
A client-side generated primary key that follows the MurmurHash (MurmurHash3) spec.
https://en.wikipedia.org/wiki/MurmurHash
"""
@staticmethod
def create_pk(self, *args, **kwargs) -> str:
return str('some argument in args / kwargs')
and set Meta primary_key_creator_cls like this:
class ErrorServer(HashModel):
local_hostname: str
class Meta:
primary_key_creator_cls = Mmh3PrimaryKey
But when I instantiate ErrorServerclass
, no parameter (len of *args, **kwargs == 0) are passed to create_pk
es = ErrorServer(local_hostname='my_hostname', param2='Test')
Thanks, I'll have a go at looking into this, I see where primary_key_creator_cls
is handled here https://github.com/redis/redis-om-python/blob/main/aredis_om/model/model.py#L856 but will need to figure out why no params are passed into create_pk
.
If it helps, I found that in model.py
, validate_pk(cls, v)
call create_pk()
without any arguments.
https://github.com/redis/redis-om-python/blob/main/aredis_om/model/model.py#L1136
@validator("pk", always=True, allow_reuse=True)
def validate_pk(cls, v):
if not v:
v = cls._meta.primary_key_creator_cls().create_pk()
return v
It's call in __init__(...)
with **data
euqual to kwargs
https://github.com/redis/redis-om-python/blob/main/aredis_om/model/model.py#L1104
def __init__(__pydantic_self__, **data: Any) -> None:
super().__init__(**data)
See in Pydantic.validators, validators are “class methods”, and full signature here is equal to (cls, value, values, config, field). In other word, def validate_pk(cls, v, values, **kwargs):
is euqal to config, field.
I am also interested in changing the default primary key and noticed the same behavior.
I also noticed that the custom class FieldInfo
(inherited from Pydantic FieldInfo) has the attribute "primary_key" which is used in the RedisModel class to initiate the default field pk
as primary key.
But apparently this attribute cannot be used elsewhere:
Class Customer(HashModel):
email: EmailStr = Field(index=True, primary_key=True)
--
File ~/.local/lib/python3.9/site-packages/redis_om/model/model.py:1158, in RedisModel.validate_primary_key(cls)
1156 raise RedisModelError("You must define a primary key for the model")
1157 elif primary_keys > 1:
-> 1158 raise RedisModelError("You must define only one primary key for a model")
RedisModelError: You must define only one primary key for a model
It would be great to use this parameter like SQL Alchemy ORM to change the default behavior. If we use this parameter manually in another field (declared one time in the model), the latter must have precedence over the pk field. What do you think?
I was having the same issue and until this is fixed you can work around it like this:
class ErrorServer(HashModel):
local_hostname: str
es = ErrorServer(pk=Mmh3PrimaryKey.create_pk(some_arg), local_hostname='my_hostname', param2='Test')
I also need to change the default pk. I use this workaround. Works fine for me.
from pydantic import root_validator
class Foo(JsonModel):
name: str
@root_validator(pre=True)
def overwrite_pk(cls, values: dict[str, Any]):
def _overwrite_pk(*args, **kwargs):
return f"{args[0]}.{args[1]}"
values["pk"] = _overwrite_pk(values["name"])
return values
foo = Foo(name="test")
print(foo.pk) # -> "test"
After reading the source code, I found that you can either do
def __init__(self, **data):
super().__init__(**data)
self.pk = Mmh3PrimaryKey(...)
or for other use-cases, do:
class Domain(aredis_om.HashModel):
email: str = aredis_om.Field(primary_key=True)
☝️ this works because here we pop the key from the RedisModel
based on the primary_key
field that we set here
We should mention it in the docs to help others looking for the same answer.
For other newbies to Redis-OM: I struggled with this. As I (mis-?) understood it, Redis looked-up on a key which defined a hard-to-compute object. Here's a simple example without any searching, just to store and retrieve an object.
My model:
class SomeModel(JsonModel):
def __init__(self, **data):
super().__init__(**data)
# don't want a random ULID key, wanna lookup on this pk
primary_key_pattern = "{start}:{stop}:{sensors}"
self.pk = primary_key_pattern.format(**data)
start: datetime
stop: datetime
sensor: list
result: Optional [str] # this is hard to compute!
An example of using it:
start = dt(2020, 1, 1)
stop = dt(2021, 1, 1)
q1 = SomeModel(
start=start,
stop=stop,
sensors=["Import"],
)
# Do the hard thang, then save it for laters
q1.result = "hard to compute!"
q1.expire(2)
q1.save()
# Soon after, I need it again
q1_again = SomeModel(
devices=["test"],
start=start,
stop=stop,
sensors=["Import"]
)
# phew, don't need to compute
assert SomeModel.get(q1_again.pk).result == "hard to compute!"
sleep(5)
# after timeout, this will raise NotFoundError
SomeModel.get(q1_again.pk)