doctests/search_quickstart.py (239 lines of code) (raw):
# EXAMPLE: search_quickstart
# HIDE_START
"""
Code samples for document database quickstart pages:
https://redis.io/docs/latest/develop/get-started/document-database/
"""
import redis
import redis.commands.search.aggregation as aggregations
import redis.commands.search.reducers as reducers
from redis.commands.json.path import Path
from redis.commands.search.field import NumericField, TagField, TextField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
from redis.commands.search.query import Query
# HIDE_END
# STEP_START connect
r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
# STEP_END
# REMOVE_START
try:
r.ft("idx:bicycle").dropindex()
except Exception:
pass
# REMOVE_END
# STEP_START data_sample
bicycle = {
"brand": "Velorim",
"model": "Jigger",
"price": 270,
"description": (
"Small and powerful, the Jigger is the best ride "
"for the smallest of tikes! This is the tiniest "
"kids’ pedal bike on the market available without"
" a coaster brake, the Jigger is the vehicle of "
"choice for the rare tenacious little rider "
"raring to go."
),
"condition": "new",
}
# STEP_END
bicycles = [
bicycle,
{
"brand": "Bicyk",
"model": "Hillcraft",
"price": 1200,
"description": (
"Kids want to ride with as little weight as possible."
" Especially on an incline! They may be at the age "
'when a 27.5" wheel bike is just too clumsy coming '
'off a 24" bike. The Hillcraft 26 is just the solution'
" they need!"
),
"condition": "used",
},
{
"brand": "Nord",
"model": "Chook air 5",
"price": 815,
"description": (
"The Chook Air 5 gives kids aged six years and older "
"a durable and uberlight mountain bike for their first"
" experience on tracks and easy cruising through forests"
" and fields. The lower top tube makes it easy to mount"
" and dismount in any situation, giving your kids greater"
" safety on the trails."
),
"condition": "used",
},
{
"brand": "Eva",
"model": "Eva 291",
"price": 3400,
"description": (
"The sister company to Nord, Eva launched in 2005 as the"
" first and only women-dedicated bicycle brand. Designed"
" by women for women, allEva bikes are optimized for the"
" feminine physique using analytics from a body metrics"
" database. If you like 29ers, try the Eva 291. It’s a "
"brand new bike for 2022.. This full-suspension, "
"cross-country ride has been designed for velocity. The"
" 291 has 100mm of front and rear travel, a superlight "
"aluminum frame and fast-rolling 29-inch wheels. Yippee!"
),
"condition": "used",
},
{
"brand": "Noka Bikes",
"model": "Kahuna",
"price": 3200,
"description": (
"Whether you want to try your hand at XC racing or are "
"looking for a lively trail bike that's just as inspiring"
" on the climbs as it is over rougher ground, the Wilder"
" is one heck of a bike built specifically for short women."
" Both the frames and components have been tweaked to "
"include a women’s saddle, different bars and unique "
"colourway."
),
"condition": "used",
},
{
"brand": "Breakout",
"model": "XBN 2.1 Alloy",
"price": 810,
"description": (
"The XBN 2.1 Alloy is our entry-level road bike – but that’s"
" not to say that it’s a basic machine. With an internal "
"weld aluminium frame, a full carbon fork, and the slick-shifting"
" Claris gears from Shimano’s, this is a bike which doesn’t"
" break the bank and delivers craved performance."
),
"condition": "new",
},
{
"brand": "ScramBikes",
"model": "WattBike",
"price": 2300,
"description": (
"The WattBike is the best e-bike for people who still feel young"
" at heart. It has a Bafang 1000W mid-drive system and a 48V"
" 17.5AH Samsung Lithium-Ion battery, allowing you to ride for"
" more than 60 miles on one charge. It’s great for tackling hilly"
" terrain or if you just fancy a more leisurely ride. With three"
" working modes, you can choose between E-bike, assisted bicycle,"
" and normal bike modes."
),
"condition": "new",
},
{
"brand": "Peaknetic",
"model": "Secto",
"price": 430,
"description": (
"If you struggle with stiff fingers or a kinked neck or back after"
" a few minutes on the road, this lightweight, aluminum bike"
" alleviates those issues and allows you to enjoy the ride. From"
" the ergonomic grips to the lumbar-supporting seat position, the"
" Roll Low-Entry offers incredible comfort. The rear-inclined seat"
" tube facilitates stability by allowing you to put a foot on the"
" ground to balance at a stop, and the low step-over frame makes it"
" accessible for all ability and mobility levels. The saddle is"
" very soft, with a wide back to support your hip joints and a"
" cutout in the center to redistribute that pressure. Rim brakes"
" deliver satisfactory braking control, and the wide tires provide"
" a smooth, stable ride on paved roads and gravel. Rack and fender"
" mounts facilitate setting up the Roll Low-Entry as your preferred"
" commuter, and the BMX-like handlebar offers space for mounting a"
" flashlight, bell, or phone holder."
),
"condition": "new",
},
{
"brand": "nHill",
"model": "Summit",
"price": 1200,
"description": (
"This budget mountain bike from nHill performs well both on bike"
" paths and on the trail. The fork with 100mm of travel absorbs"
" rough terrain. Fat Kenda Booster tires give you grip in corners"
" and on wet trails. The Shimano Tourney drivetrain offered enough"
" gears for finding a comfortable pace to ride uphill, and the"
" Tektro hydraulic disc brakes break smoothly. Whether you want an"
" affordable bike that you can take to work, but also take trail in"
" mountains on the weekends or you’re just after a stable,"
" comfortable ride for the bike path, the Summit gives a good value"
" for money."
),
"condition": "new",
},
{
"model": "ThrillCycle",
"brand": "BikeShind",
"price": 815,
"description": (
"An artsy, retro-inspired bicycle that’s as functional as it is"
" pretty: The ThrillCycle steel frame offers a smooth ride. A"
" 9-speed drivetrain has enough gears for coasting in the city, but"
" we wouldn’t suggest taking it to the mountains. Fenders protect"
" you from mud, and a rear basket lets you transport groceries,"
" flowers and books. The ThrillCycle comes with a limited lifetime"
" warranty, so this little guy will last you long past graduation."
),
"condition": "refurbished",
},
]
# STEP_START create_index
schema = (
TextField("$.brand", as_name="brand"),
TextField("$.model", as_name="model"),
TextField("$.description", as_name="description"),
NumericField("$.price", as_name="price"),
TagField("$.condition", as_name="condition"),
)
index = r.ft("idx:bicycle")
index.create_index(
schema,
definition=IndexDefinition(prefix=["bicycle:"], index_type=IndexType.JSON),
)
# STEP_END
# STEP_START add_documents
for bid, bicycle in enumerate(bicycles):
r.json().set(f"bicycle:{bid}", Path.root_path(), bicycle)
# STEP_END
# STEP_START wildcard_query
res = index.search(Query("*"))
print("Documents found:", res.total)
# >>> Documents found: 10
# STEP_END
# REMOVE_START
assert res.total == 10
# REMOVE_END
# STEP_START query_single_term
res = index.search(Query("@model:Jigger"))
print(res)
# >>> Result{1 total, docs: [
# Document {
# 'id': 'bicycle:0',
# 'payload': None,
# 'json': '{
# "brand":"Velorim",
# "model":"Jigger",
# "price":270,
# ...
# "condition":"new"
# }'
# }]}
# STEP_END
# REMOVE_START
assert res.docs[0].id == "bicycle:0"
# REMOVE_END
# STEP_START query_single_term_limit_fields
res = index.search(Query("@model:Jigger").return_field("$.price", as_field="price"))
print(res)
# >>> [Document {'id': 'bicycle:0', 'payload': None, 'price': '270'}]
# STEP_END
# REMOVE_START
assert res.docs[0].id == "bicycle:0"
# REMOVE_END
# STEP_START query_single_term_and_num_range
res = index.search(Query("basic @price:[500 1000]"))
print(res)
# >>> Result{1 total, docs: [
# Document {
# 'id': 'bicycle:5',
# 'payload': None,
# 'json': '{
# "brand":"Breakout",
# "model":"XBN 2.1 Alloy",
# "price":810,
# ...
# "condition":"new"
# }'
# }]}
# STEP_END
# REMOVE_START
assert res.docs[0].id == "bicycle:5"
# REMOVE_END
# STEP_START query_exact_matching
res = index.search(Query('@brand:"Noka Bikes"'))
print(res)
# >>> Result{1 total, docs: [
# Document {
# 'id': 'bicycle:4',
# 'payload': None,
# 'json': '{
# "brand":"Noka Bikes",
# "model":"Kahuna",
# "price":3200,
# ...
# "condition":"used"
# }'
# }]}
# STEP_END
# REMOVE_START
assert res.docs[0].id == "bicycle:4"
# REMOVE_END
# STEP_START query_fuzzy_matching
res = index.search(
Query("@description:%analitics%").dialect( # Note the typo in the word "analytics"
2
)
)
print(res)
# >>> Result{1 total, docs: [
# Document {
# 'id': 'bicycle:3',
# 'payload': None,
# 'json': '{
# "brand":"Eva",
# "model":"Eva 291",
# "price":3400,
# "description":"...using analytics from a body metrics database...",
# "condition":"used"
# }'
# }]}
# STEP_END
# REMOVE_START
assert res.docs[0].id == "bicycle:3"
# REMOVE_END
# STEP_START query_fuzzy_matching_level2
res = index.search(
Query("@description:%%analitycs%%").dialect( # Note 2 typos in the word "analytics"
2
)
)
print(res)
# >>> Result{1 total, docs: [
# Document {
# 'id': 'bicycle:3',
# 'payload': None,
# 'json': '{
# "brand":"Eva",
# "model":"Eva 291",
# "price":3400,
# "description":"...using analytics from a body metrics database...",
# "condition":"used"
# }'
# }]}
# STEP_END
# REMOVE_START
assert res.docs[0].id == "bicycle:3"
# REMOVE_END
# STEP_START query_prefix_matching
res = index.search(Query("@model:hill*"))
print(res)
# >>> Result{1 total, docs: [
# Document {
# 'id': 'bicycle:1',
# 'payload': None,
# 'json': '{
# "brand":"Bicyk",
# "model":"Hillcraft",
# "price":1200,
# ...
# "condition":"used"
# }'
# }]}
# STEP_END
# REMOVE_START
assert res.docs[0].id == "bicycle:1"
# REMOVE_END
# STEP_START query_suffix_matching
res = index.search(Query("@model:*bike"))
print(res)
# >>> Result{1 total, docs: [
# Document {
# 'id': 'bicycle:6',
# 'payload': None,
# 'json': '{
# "brand":"ScramBikes",
# "model":"WattBike",
# "price":2300,
# ...
# "condition":"new"
# }'
# }]}
# STEP_END
# REMOVE_START
assert res.docs[0].id == "bicycle:6"
# REMOVE_END
# STEP_START query_wildcard_matching
res = index.search(Query("w'H?*craft'").dialect(2))
print(res.docs[0].json)
# >>> {
# "brand":"Bicyk",
# "model":"Hillcraft",
# "price":1200,
# ...
# "condition":"used"
# }
# STEP_END
# REMOVE_START
assert res.docs[0].id == "bicycle:1"
# REMOVE_END
# STEP_START query_with_default_scorer
res = index.search(Query("mountain").with_scores())
for sr in res.docs:
print(f"{sr.id}: score={sr.score}")
# STEP_END
# REMOVE_START
assert res.total == 3
# REMOVE_END
# STEP_START query_with_bm25_scorer
res = index.search(Query("mountain").with_scores().scorer("BM25"))
for sr in res.docs:
print(f"{sr.id}: score={sr.score}")
# STEP_END
# REMOVE_START
assert res.total == 3
assert res.docs[0].score == res.docs[1].score
# REMOVE_END
# STEP_START simple_aggregation
req = aggregations.AggregateRequest("*").group_by(
"@condition", reducers.count().alias("count")
)
res = index.aggregate(req).rows
print(res)
# >>> [['condition', 'refurbished', 'count', '1'],
# ['condition', 'used', 'count', '4'],
# ['condition', 'new', 'count', '5']]
# STEP_END
# REMOVE_START
assert len(res) == 3
# REMOVE_END