Skip to content

Commit 1304e53

Browse files
authored
Merge branch 'main' into huwaiza/vector-field-fix
2 parents 14ab9dc + 277ed25 commit 1304e53

File tree

10 files changed

+176
-9
lines changed

10 files changed

+176
-9
lines changed

.github/wordlist.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ validator
5656
validators
5757
virtualenv
5858
http
59+
AMR
60+
EntraID
61+
entraid
62+
metamodel

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
strategy:
7777
matrix:
7878
os: [ ubuntu-latest ]
79-
pyver: [ "3.8", "3.9", "3.10", "3.11", "3.12", "pypy-3.8", "pypy-3.9", "pypy-3.10" ]
79+
pyver: [ "3.9", "3.10", "3.11", "3.12", "pypy-3.9", "pypy-3.10" ]
8080
redisstack: [ "latest" ]
8181
fail-fast: false
8282
services:

.github/workflows/pypi-publish.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
4343
- name: Set Poetry config
4444
run: |
45-
pip install --upgrade pip poetry
45+
pip install --upgrade pip poetry==1.8.4
4646
poetry config virtualenvs.in-project false
4747
poetry config virtualenvs.path ~/.virtualenvs
4848
poetry export --dev --without-hashes -o requirements.txt

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div align="center">
22
<br/>
33
<br/>
4-
<img width="360" src="https://raw.githubusercontent.com/redis-developer/redis-om-python/main/images/logo.svg?token=AAAXXHUYL6RHPESRRAMBJOLBSVQXE" alt="Redis OM" />
4+
<img width="360" src="docs/images/logo.svg" alt="Redis OM" />
55
<br/>
66
<br/>
77
</div>
@@ -370,6 +370,36 @@ You can run these modules in your self-hosted Redis deployment, or you can use [
370370

371371
To learn more, read [our documentation](docs/redis_modules.md).
372372

373+
## Connecting to Azure Managed Redis with EntraID
374+
375+
Once you have EntraID setup for your Azure Managed Redis (AMR) deployment, you can use Redis OM Python with it by leveraging the [redis-py-entraid](https://github.com/redis/redis-py-entraid) library.
376+
Simply install the library with:
377+
378+
```
379+
pip install redis-entraid
380+
```
381+
382+
Then you'll initialize your credentials provider, connect to redis, and pass the database object into your metamodel:
383+
384+
```python
385+
386+
from redis import Redis
387+
from redis_entraid.cred_provider import create_from_default_azure_credential
388+
from redis_om import HashModel, Field
389+
credential_provider = create_from_default_azure_credential(
390+
("https://redis.azure.com/.default",),
391+
)
392+
393+
db = Redis(host="cluster-name.region.redis.azure.net", port=10000, ssl=True, ssl_cert_reqs=None, credential_provider=credential_provider)
394+
db.flushdb()
395+
class User(HashModel):
396+
first_name: str
397+
last_name: str = Field(index=True)
398+
399+
class Meta:
400+
database = db
401+
```
402+
373403
## ❤️ Contributing
374404

375405
We'd love your contributions!

aredis_om/model/migrations/migrator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ async def detect_migrations(self):
130130
continue
131131

132132
stored_hash = await conn.get(hash_key)
133+
if isinstance(stored_hash, bytes):
134+
stored_hash = stored_hash.decode('utf-8')
135+
133136
schema_out_of_date = current_hash != stored_hash
134137

135138
if schema_out_of_date:

aredis_om/model/model.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,13 @@ def __new__(cls, name, bases, attrs, **kwargs): # noqa C901
13201320
meta = meta or getattr(new_class, "Meta", None)
13211321
base_meta = getattr(new_class, "_meta", None)
13221322

1323+
if len(bases) >= 1:
1324+
for base_index in range(len(bases)):
1325+
model_fields = getattr(bases[base_index], "model_fields", [])
1326+
for f_name in model_fields:
1327+
field = model_fields[f_name]
1328+
new_class.model_fields[f_name] = field
1329+
13231330
if meta and meta != DefaultMeta and meta != base_meta:
13241331
new_class.Meta = meta
13251332
new_class._meta = meta
@@ -1455,7 +1462,16 @@ class Config:
14551462

14561463
def __init__(__pydantic_self__, **data: Any) -> None:
14571464
__pydantic_self__.validate_primary_key()
1458-
super().__init__(**data)
1465+
missing_fields = __pydantic_self__.model_fields.keys() - data.keys() - {"pk"}
1466+
1467+
kwargs = data.copy()
1468+
1469+
# This is a hack, we need to manually make sure we are setting up defaults correctly when we encounter them
1470+
# because inheritance apparently won't cover that in pydantic 2.0.
1471+
for field in missing_fields:
1472+
default_value = __pydantic_self__.model_fields.get(field).default # type: ignore
1473+
kwargs[field] = default_value
1474+
super().__init__(**kwargs)
14591475

14601476
def __lt__(self, other):
14611477
"""Default sort: compare primary key of models."""
@@ -1527,7 +1543,7 @@ def validate_primary_key(cls):
15271543
else:
15281544
field_info = field.field_info
15291545

1530-
if getattr(field_info, "primary_key", None):
1546+
if getattr(field_info, "primary_key", None) is True:
15311547
primary_keys += 1
15321548
if primary_keys == 0:
15331549
raise RedisModelError("You must define a primary key for the model")
@@ -1808,7 +1824,7 @@ def schema_for_fields(cls):
18081824
else:
18091825
field_info = field.field_info
18101826

1811-
if getattr(field_info, "primary_key", None):
1827+
if getattr(field_info, "primary_key", None) is True:
18121828
if issubclass(_type, str):
18131829
redisearch_field = (
18141830
f"{name} TAG SEPARATOR {SINGLE_VALUE_TAG_FIELD_SEPARATOR}"
@@ -2003,7 +2019,7 @@ def schema_for_fields(cls):
20032019
field_info = field.field_info
20042020
else:
20052021
field_info = field
2006-
if getattr(field_info, "primary_key", None):
2022+
if getattr(field_info, "primary_key", None) is True:
20072023
if issubclass(_type, str):
20082024
redisearch_field = f"$.{name} AS {name} TAG SEPARATOR {SINGLE_VALUE_TAG_FIELD_SEPARATOR}"
20092025
else:

docs/images/logo.svg

Lines changed: 1 addition & 0 deletions
Loading

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "redis-om"
3-
version = "0.3.3"
3+
version = "0.3.5"
44
description = "Object mappings, and more, for Redis."
55
authors = ["Redis OSS <[email protected]>"]
66
maintainers = ["Redis OSS <[email protected]>"]
@@ -44,7 +44,7 @@ python-ulid = "^1.0.3"
4444
typing-extensions = "^4.4.0"
4545
hiredis = ">=2.2.3,<4.0.0"
4646
more-itertools = ">=8.14,<11.0"
47-
setuptools = {version = ">=70.0,<76.0", markers = "python_version >= '3.12'"}
47+
setuptools = ">=70.0"
4848

4949
[tool.poetry.dev-dependencies]
5050
mypy = "^1.9.0"

tests/test_hash_model.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
Migrator,
1919
NotFoundError,
2020
QueryNotSupportedError,
21+
RedisModel,
2122
RedisModelError,
2223
)
2324

@@ -951,3 +952,53 @@ class TestLiterals(HashModel):
951952
await item.save()
952953
rematerialized = await TestLiterals.find(TestLiterals.flavor == "pumpkin").first()
953954
assert rematerialized.pk == item.pk
955+
956+
957+
@py_test_mark_asyncio
958+
async def test_child_class_expression_proxy():
959+
# https://github.com/redis/redis-om-python/issues/669 seeing weird issue with child classes initalizing all their undefined members as ExpressionProxies
960+
class Model(HashModel):
961+
first_name: str
962+
last_name: str
963+
age: int = Field(default=18)
964+
bio: Optional[str] = Field(default=None)
965+
966+
class Child(Model):
967+
other_name: str
968+
# is_new: bool = Field(default=True)
969+
970+
await Migrator().run()
971+
m = Child(first_name="Steve", last_name="Lorello", other_name="foo")
972+
await m.save()
973+
print(m.age)
974+
assert m.age == 18
975+
976+
rematerialized = await Child.find(Child.pk == m.pk).first()
977+
978+
assert rematerialized.age == 18
979+
assert rematerialized.bio is None
980+
981+
982+
@py_test_mark_asyncio
983+
async def test_child_class_expression_proxy_with_mixin():
984+
# https://github.com/redis/redis-om-python/issues/669 seeing weird issue with child classes initalizing all their undefined members as ExpressionProxies
985+
class Model(RedisModel, abc.ABC):
986+
first_name: str
987+
last_name: str
988+
age: int = Field(default=18)
989+
bio: Optional[str] = Field(default=None)
990+
991+
class Child(Model, HashModel):
992+
other_name: str
993+
# is_new: bool = Field(default=True)
994+
995+
await Migrator().run()
996+
m = Child(first_name="Steve", last_name="Lorello", other_name="foo")
997+
await m.save()
998+
print(m.age)
999+
assert m.age == 18
1000+
1001+
rematerialized = await Child.find(Child.pk == m.pk).first()
1002+
1003+
assert rematerialized.age == 18
1004+
assert rematerialized.bio is None

tests/test_json_model.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import pytest
1414
import pytest_asyncio
15+
from more_itertools.more import first
1516
from redis.exceptions import ResponseError
1617

1718
from aredis_om import (
@@ -21,6 +22,7 @@
2122
Migrator,
2223
NotFoundError,
2324
QueryNotSupportedError,
25+
RedisModel,
2426
RedisModelError,
2527
VectorFieldOptions,
2628
)
@@ -1161,6 +1163,66 @@ class TestLiterals(JsonModel):
11611163
assert rematerialized.pk == item.pk
11621164

11631165

1166+
@py_test_mark_asyncio
1167+
async def test_two_false_pks():
1168+
from pydantic_core import PydanticUndefined as Undefined
1169+
1170+
class SomeModel(JsonModel):
1171+
field1: str = Field(index=True, primary_key=Undefined)
1172+
field2: str = Field(index=True, primary_key=Undefined)
1173+
1174+
SomeModel(field1="foo", field2="bar")
1175+
1176+
1177+
@py_test_mark_asyncio
1178+
async def test_child_class_expression_proxy():
1179+
# https://github.com/redis/redis-om-python/issues/669 seeing weird issue with child classes initalizing all their undefined members as ExpressionProxies
1180+
class Model(JsonModel):
1181+
first_name: str
1182+
last_name: str
1183+
age: int = Field(default=18)
1184+
bio: Optional[str] = Field(default=None)
1185+
1186+
class Child(Model):
1187+
is_new: bool = Field(default=True)
1188+
1189+
await Migrator().run()
1190+
m = Child(first_name="Steve", last_name="Lorello")
1191+
await m.save()
1192+
print(m.age)
1193+
assert m.age == 18
1194+
1195+
rematerialized = await Child.find(Child.pk == m.pk).first()
1196+
1197+
assert rematerialized.age == 18
1198+
assert rematerialized.age != 19
1199+
assert rematerialized.bio is None
1200+
1201+
1202+
@py_test_mark_asyncio
1203+
async def test_child_class_expression_proxy_with_mixin():
1204+
class Model(RedisModel, abc.ABC):
1205+
first_name: str
1206+
last_name: str
1207+
age: int = Field(default=18)
1208+
bio: Optional[str] = Field(default=None)
1209+
1210+
class Child(Model, JsonModel):
1211+
is_new: bool = Field(default=True)
1212+
1213+
await Migrator().run()
1214+
m = Child(first_name="Steve", last_name="Lorello")
1215+
await m.save()
1216+
print(m.age)
1217+
assert m.age == 18
1218+
1219+
rematerialized = await Child.find(Child.pk == m.pk).first()
1220+
1221+
assert rematerialized.age == 18
1222+
assert rematerialized.age != 19
1223+
assert rematerialized.bio is None
1224+
1225+
11641226
@py_test_mark_asyncio
11651227
async def test_merged_model_error():
11661228
class Player(EmbeddedJsonModel):

0 commit comments

Comments
 (0)