Skip to content

Commit 5c1dbc7

Browse files
authored
Fix has_redisearch await and add py.typed marker (#771)
* Fix has_redisearch missing await and add py.typed marker * Update migration guide with Python 3.10+ and Pydantic v2 requirements * Add 1.0 release notes * Add technical terms to spellcheck wordlist * Add coroutine to wordlist * Fix CLI bugs: check-schema parameter and remove incomplete stats command * Remove deprecated legacy migrate CLI command * Refactor migration system: rename Migrator to SchemaDetector, add index CLI * Add om migrate downgrade command * Update migration guide with CLI and Migrator breaking changes * Add compat and programmatically to spellcheck wordlist
1 parent 5c14ce1 commit 5c1dbc7

18 files changed

Lines changed: 653 additions & 268 deletions

.github/wordlist.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,12 @@ github
101101
ULID
102102
booleans
103103
instantiation
104-
MyModel
104+
MyModel
105+
nx
106+
xx
107+
FieldInfo
108+
issubclass
109+
ulid
110+
coroutine
111+
compat
112+
programmatically

aredis_om/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from .async_redis import redis # isort:skip
22
from .checks import has_redis_json, has_redisearch
33
from .connections import get_redis_connection
4-
from .model.migrations.schema.legacy_migrator import MigrationError, Migrator
4+
from .model.migrations import MigrationError, SchemaMigrator
5+
from .model.migrations.schema.legacy_migrator import SchemaDetector
56
from .model.model import (
67
EmbeddedJsonModel,
78
Field,
@@ -18,6 +19,10 @@
1819
)
1920
from .model.types import Coordinates, GeoFilter
2021

22+
23+
# Backward compatibility alias - deprecated, use SchemaDetector or SchemaMigrator
24+
Migrator = SchemaDetector
25+
2126
__all__ = [
2227
"Coordinates",
2328
"EmbeddedJsonModel",
@@ -28,12 +33,14 @@
2833
"JsonModel",
2934
"KNNExpression",
3035
"MigrationError",
31-
"Migrator",
36+
"Migrator", # Deprecated - use SchemaMigrator for production
3237
"NotFoundError",
3338
"QueryNotSupportedError",
3439
"QuerySyntaxError",
3540
"RedisModel",
3641
"RedisModelError",
42+
"SchemaMigrator",
43+
"SchemaDetector",
3744
"VectorFieldOptions",
3845
"get_redis_connection",
3946
"has_redis_json",

aredis_om/checks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ async def has_redis_json(conn=None):
2121
async def has_redisearch(conn=None):
2222
if conn is None:
2323
conn = get_redis_connection()
24-
if has_redis_json(conn):
24+
if await has_redis_json(conn):
2525
return True
2626
command_exists = await check_for_command(conn, "ft.search")
2727
return command_exists

aredis_om/cli/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import click
66

7+
from ..model.cli.index import index
78
from ..model.cli.migrate import migrate
89
from ..model.cli.migrate_data import migrate_data
910

@@ -16,6 +17,7 @@ def om():
1617

1718

1819
# Add subcommands
20+
om.add_command(index)
1921
om.add_command(migrate)
2022
om.add_command(migrate_data, name="migrate-data")
2123

aredis_om/model/cli/index.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
"""
2+
CLI for managing Redis OM indexes.
3+
4+
Provides commands for creating, dropping, and rebuilding RediSearch indexes
5+
during development. For production migrations, use `om migrate` instead.
6+
"""
7+
8+
import asyncio
9+
from typing import Optional
10+
11+
import click
12+
13+
14+
def run_async(coro):
15+
"""Run an async coroutine in an isolated event loop."""
16+
import concurrent.futures
17+
18+
with concurrent.futures.ThreadPoolExecutor() as executor:
19+
future = executor.submit(asyncio.run, coro)
20+
return future.result()
21+
22+
23+
@click.group()
24+
def index():
25+
"""Manage RediSearch indexes for Redis OM models.
26+
27+
These commands are intended for development workflows. For production
28+
deployments, use `om migrate` for tracked, reversible migrations.
29+
"""
30+
pass
31+
32+
33+
@index.command()
34+
@click.option("--module", "-m", help="Python module containing models to import")
35+
@click.option(
36+
"--force", "-f", is_flag=True, help="Force recreation of existing indexes"
37+
)
38+
def create(module: Optional[str], force: bool):
39+
"""Create indexes for all registered models.
40+
41+
This will create RediSearch indexes for all models that don't have
42+
an existing index (unless --force is used to recreate).
43+
"""
44+
from ..migrations.schema.legacy_migrator import SchemaDetector
45+
46+
async def _create():
47+
from ...connections import get_redis_connection
48+
49+
conn = get_redis_connection()
50+
detector = SchemaDetector(module=module, conn=conn)
51+
await detector.detect_migrations()
52+
53+
if not detector.migrations:
54+
click.echo("✅ All indexes are up to date.")
55+
return
56+
57+
created = 0
58+
for migration in detector.migrations:
59+
if migration.action.name == "CREATE":
60+
click.echo(f"Creating index: {migration.index_name}")
61+
await migration.run()
62+
created += 1
63+
elif force and migration.action.name == "DROP":
64+
click.echo(f"Dropping index: {migration.index_name}")
65+
await migration.run()
66+
67+
click.echo(f"\n✅ Created {created} index(es).")
68+
69+
run_async(_create())
70+
71+
72+
@index.command()
73+
@click.option("--module", "-m", help="Python module containing models to import")
74+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
75+
def drop(module: Optional[str], yes: bool):
76+
"""Drop all indexes for registered models.
77+
78+
Warning: This is destructive and will remove all indexes. Data remains
79+
but will not be searchable until indexes are recreated.
80+
"""
81+
from ..migrations.schema.legacy_migrator import import_submodules
82+
83+
async def _drop():
84+
from ...connections import get_redis_connection
85+
from ...model.model import model_registry
86+
87+
if module:
88+
import_submodules(module)
89+
90+
conn = get_redis_connection()
91+
92+
if not model_registry:
93+
click.echo("No models found in registry.")
94+
return
95+
96+
indexes = [cls.Meta.index_name for cls in model_registry.values()]
97+
98+
if not yes:
99+
click.echo(f"This will drop {len(indexes)} index(es):")
100+
for idx in indexes:
101+
click.echo(f"- {idx}")
102+
if not click.confirm("\nContinue?"):
103+
click.echo("Aborted.")
104+
return
105+
106+
dropped = 0
107+
for idx in indexes:
108+
try:
109+
await conn.ft(idx).dropindex()
110+
click.echo(f"Dropped: {idx}")
111+
dropped += 1
112+
except Exception:
113+
click.echo(f"Skipped (not found): {idx}")
114+
115+
click.echo(f"\n✅ Dropped {dropped} index(es).")
116+
117+
run_async(_drop())
118+
119+
120+
@index.command()
121+
@click.option("--module", "-m", help="Python module containing models to import")
122+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
123+
def rebuild(module: Optional[str], yes: bool):
124+
"""Drop and recreate all indexes.
125+
126+
This is a convenience command for development that combines `drop` and
127+
`create`. All indexes will be dropped and recreated from current model
128+
definitions.
129+
"""
130+
from ..migrations.schema.legacy_migrator import SchemaDetector, import_submodules
131+
132+
async def _rebuild():
133+
from ...connections import get_redis_connection
134+
from ...model.model import model_registry
135+
136+
if module:
137+
import_submodules(module)
138+
139+
conn = get_redis_connection()
140+
141+
if not model_registry:
142+
click.echo("No models found in registry.")
143+
return
144+
145+
indexes = [(name, cls) for name, cls in model_registry.items()]
146+
147+
if not yes:
148+
click.echo(f"This will rebuild {len(indexes)} index(es):")
149+
for name, cls in indexes:
150+
click.echo(f"- {cls.Meta.index_name}")
151+
if not click.confirm("\nContinue?"):
152+
click.echo("Aborted.")
153+
return
154+
155+
# Drop all indexes
156+
for name, cls in indexes:
157+
try:
158+
await conn.ft(cls.Meta.index_name).dropindex()
159+
click.echo(f"Dropped: {cls.Meta.index_name}")
160+
except Exception: # nosec B110
161+
pass # Index didn't exist, ignore
162+
163+
# Create all indexes
164+
detector = SchemaDetector(module=module, conn=conn)
165+
await detector.detect_migrations()
166+
167+
for migration in detector.migrations:
168+
if migration.action.name == "CREATE":
169+
click.echo(f"Creating: {migration.index_name}")
170+
await migration.run()
171+
172+
click.echo(f"\n✅ Rebuilt {len(indexes)} index(es).")
173+
174+
run_async(_rebuild())

aredis_om/model/cli/legacy_migrate.py

Lines changed: 0 additions & 123 deletions
This file was deleted.

0 commit comments

Comments
 (0)