Skip to content

Commit 551429c

Browse files
wiseaidevchayimdvora-h
authored
Allow users to define a new primary key. (#347)
* make primary key programmable Signed-off-by: wiseaidev <[email protected]> * get primary key field using the `key` method Signed-off-by: wiseaidev <[email protected]> * adjust delete_many & expire methods Signed-off-by: wiseaidev <[email protected]> * fix query for int primary key Signed-off-by: wiseaidev <[email protected]> * fix grammar Signed-off-by: wiseaidev <[email protected]> * add unit tests Signed-off-by: wiseaidev <[email protected]> Signed-off-by: wiseaidev <[email protected]> Co-authored-by: Chayim <[email protected]> Co-authored-by: dvora-h <[email protected]>
1 parent 2e09234 commit 551429c

File tree

2 files changed

+74
-13
lines changed

2 files changed

+74
-13
lines changed

aredis_om/model/model.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,10 @@ def resolve_value(
564564
separator_char,
565565
)
566566
return ""
567-
if separator_char in value:
567+
if isinstance(value, int):
568+
# This if will hit only if the field is a primary key of type int
569+
result = f"@{field_name}:[{value} {value}]"
570+
elif separator_char in value:
568571
# The value contains the TAG field separator. We can work
569572
# around this by breaking apart the values and unioning them
570573
# with multiple field:{} queries.
@@ -1106,12 +1109,12 @@ class Config:
11061109
extra = "allow"
11071110

11081111
def __init__(__pydantic_self__, **data: Any) -> None:
1109-
super().__init__(**data)
11101112
__pydantic_self__.validate_primary_key()
1113+
super().__init__(**data)
11111114

11121115
def __lt__(self, other):
11131116
"""Default sort: compare primary key of models."""
1114-
return self.pk < other.pk
1117+
return self.key() < other.key()
11151118

11161119
def key(self):
11171120
"""Return the Redis key for this model."""
@@ -1150,7 +1153,7 @@ async def expire(
11501153
db = self._get_db(pipeline)
11511154

11521155
# TODO: Wrap any Redis response errors in a custom exception?
1153-
await db.expire(self.make_primary_key(self.pk), num_seconds)
1156+
await db.expire(self.key(), num_seconds)
11541157

11551158
@validator("pk", always=True, allow_reuse=True)
11561159
def validate_pk(cls, v):
@@ -1167,7 +1170,9 @@ def validate_primary_key(cls):
11671170
primary_keys += 1
11681171
if primary_keys == 0:
11691172
raise RedisModelError("You must define a primary key for the model")
1170-
elif primary_keys > 1:
1173+
elif primary_keys == 2:
1174+
cls.__fields__.pop('pk')
1175+
elif primary_keys > 2:
11711176
raise RedisModelError("You must define only one primary key for a model")
11721177

11731178
@classmethod
@@ -1275,7 +1280,7 @@ async def delete_many(
12751280
db = cls._get_db(pipeline)
12761281

12771282
for chunk in ichunked(models, 100):
1278-
pks = [cls.make_primary_key(model.pk) for model in chunk]
1283+
pks = [model.key() for model in chunk]
12791284
await cls._delete(db, *pks)
12801285

12811286
return len(models)

tests/test_hash_model.py

+63-7
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class Order(BaseHashModel):
4646
created_on: datetime.datetime
4747

4848
class Member(BaseHashModel):
49-
id: int = Field(index=True)
49+
id: int = Field(index=True, primary_key=True)
5050
first_name: str = Field(index=True)
5151
last_name: str = Field(index=True)
5252
email: str = Field(index=True)
@@ -445,7 +445,7 @@ async def test_saves_model_and_creates_pk(m):
445445
# Save a model instance to Redis
446446
await member.save()
447447

448-
member2 = await m.Member.get(member.pk)
448+
member2 = await m.Member.get(pk=member.id)
449449
assert member2 == member
450450

451451

@@ -495,7 +495,7 @@ async def test_delete(m):
495495
)
496496

497497
await member.save()
498-
response = await m.Member.delete(member.pk)
498+
response = await m.Member.delete(pk=member.id)
499499
assert response == 1
500500

501501

@@ -588,8 +588,8 @@ async def test_saves_many(m):
588588
result = await m.Member.add(members)
589589
assert result == [member1, member2]
590590

591-
assert await m.Member.get(pk=member1.pk) == member1
592-
assert await m.Member.get(pk=member2.pk) == member2
591+
assert await m.Member.get(pk=member1.id) == member1
592+
assert await m.Member.get(pk=member2.id) == member2
593593

594594

595595
@py_test_mark_asyncio
@@ -618,14 +618,14 @@ async def test_delete_many(m):
618618
result = await m.Member.delete_many(members)
619619
assert result == 2
620620
with pytest.raises(NotFoundError):
621-
await m.Member.get(pk=member1.pk)
621+
await m.Member.get(pk=member1.key())
622622

623623

624624
@py_test_mark_asyncio
625625
async def test_updates_a_model(members, m):
626626
member1, member2, member3 = members
627627
await member1.update(last_name="Smith")
628-
member = await m.Member.get(member1.pk)
628+
member = await m.Member.get(member1.id)
629629
assert member.last_name == "Smith"
630630

631631

@@ -681,3 +681,59 @@ class Address(m.BaseHashModel):
681681
Address.redisearch_schema()
682682
== f"ON HASH PREFIX 1 {key_prefix} SCHEMA pk TAG SEPARATOR | a_string TAG SEPARATOR | a_full_text_string TAG SEPARATOR | a_full_text_string AS a_full_text_string_fts TEXT an_integer NUMERIC SORTABLE a_float NUMERIC"
683683
)
684+
685+
686+
@py_test_mark_asyncio
687+
async def test_primary_key_model_error(m):
688+
689+
class Customer(m.BaseHashModel):
690+
id: int = Field(primary_key=True, index=True)
691+
first_name: str = Field(primary_key=True, index=True)
692+
last_name: str
693+
bio: Optional[str]
694+
695+
await Migrator().run()
696+
697+
with pytest.raises(RedisModelError, match="You must define only one primary key for a model"):
698+
_ = Customer(
699+
id=0,
700+
first_name="Mahmoud",
701+
last_name="Harmouch",
702+
bio="Python developer, wanna work at Redis, Inc."
703+
)
704+
705+
706+
@py_test_mark_asyncio
707+
async def test_primary_pk_exists(m):
708+
709+
class Customer1(m.BaseHashModel):
710+
id: int
711+
first_name: str
712+
last_name: str
713+
bio: Optional[str]
714+
715+
class Customer2(m.BaseHashModel):
716+
id: int = Field(primary_key=True, index=True)
717+
first_name: str
718+
last_name: str
719+
bio: Optional[str]
720+
721+
await Migrator().run()
722+
723+
customer = Customer1(
724+
id=0,
725+
first_name="Mahmoud",
726+
last_name="Harmouch",
727+
bio="Python developer, wanna work at Redis, Inc."
728+
)
729+
730+
assert 'pk' in customer.__fields__
731+
732+
customer = Customer2(
733+
id=1,
734+
first_name="Kim",
735+
last_name="Brookins",
736+
bio="This is member 2 who can be quite anxious until you get to know them.",
737+
)
738+
739+
assert 'pk' not in customer.__fields__

0 commit comments

Comments
 (0)