diff --git a/CHANGES b/CHANGES index 82c5b6db2a..20d7d8f51f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,4 @@ + * Move doctests (doc code examples) to main branch * Update `ResponseT` type hint * Allow to control the minimum SSL version * Add an optional lock_name attribute to LockError. diff --git a/doctests/README.md b/doctests/README.md new file mode 100644 index 0000000000..9dd6eaeb5d --- /dev/null +++ b/doctests/README.md @@ -0,0 +1,32 @@ +# Command examples for redis.io + +## How to add an example + +Create regular python file in the current folder with meaningful name. It makes sense prefix example files with +command category (e.g. string, set, list, hash, etc) to make navigation in the folder easier. Files ending in *.py* +are automatically run by the test suite. + +### Special markup + +See https://github.com/redis-stack/redis-stack-website#readme for more details. + +## How to test examples + +Examples are standalone python scripts, committed to the *doctests* directory. These scripts assume that the +```requirements.txt``` and ```dev_requirements.txt``` from this repository have been installed, as per below. + +```bash +pip install -r requirements.txt +pip install -r dev_requirements.txt +pip install -r doctests/requirements.txt +``` + +Note - the CI process, runs the basic ```black``` and ```isort``` linters against the examples. Assuming +the requirements above have been installed you can run ```black yourfile.py``` and ```isort yourfile.py``` +locally to validate the linting, prior to CI. + +Just include necessary assertions in the example file and run +```bash +sh doctests/run_examples.sh +``` +to test all examples in the current folder. diff --git a/doctests/dt_bitfield.py b/doctests/dt_bitfield.py new file mode 100644 index 0000000000..44717bf128 --- /dev/null +++ b/doctests/dt_bitfield.py @@ -0,0 +1,34 @@ +# EXAMPLE: bitfield_tutorial +# HIDE_START +""" +Code samples for Bitfield doc pages: + https://redis.io/docs/latest/develop/data-types/bitfields/ +""" +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# REMOVE_START +r.delete("bike:1:stats") +# REMOVE_END + +# STEP_START bf +bf = r.bitfield("bike:1:stats") +res1 = bf.set("u32", "#0", 1000).execute() +print(res1) # >>> [0] + +res2 = bf.incrby("u32", "#0", -50).incrby("u32", "#1", 1).execute() +print(res2) # >>> [950, 1] + +res3 = bf.incrby("u32", "#0", 500).incrby("u32", "#1", 1).execute() +print(res3) # >>> [1450, 2] + +res4 = bf.get("u32", "#0").get("u32", "#1").execute() +print(res4) # >>> [1450, 2] +# STEP_END + +# REMOVE_START +assert res1 == [0] +assert res4 == [1450, 2] +# REMOVE_END diff --git a/doctests/dt_bitmap.py b/doctests/dt_bitmap.py new file mode 100644 index 0000000000..a769c96863 --- /dev/null +++ b/doctests/dt_bitmap.py @@ -0,0 +1,40 @@ +# EXAMPLE: bitmap_tutorial +# HIDE_START +""" +Code samples for Bitmap doc pages: + https://redis.io/docs/latest/develop/data-types/bitmaps/ +""" +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# REMOVE_START +r.delete("pings:2024-01-01-00:00") +# REMOVE_END + +# STEP_START ping +res1 = r.setbit("pings:2024-01-01-00:00", 123, 1) +print(res1) # >>> 0 + +res2 = r.getbit("pings:2024-01-01-00:00", 123) +print(res2) # >>> 1 + +res3 = r.getbit("pings:2024-01-01-00:00", 456) +print(res3) # >>> 0 +# STEP_END + +# REMOVE_START +assert res1 == 0 +# REMOVE_END + +# STEP_START bitcount +# HIDE_START +r.setbit("pings:2024-01-01-00:00", 123, 1) +# HIDE_END +res4 = r.bitcount("pings:2024-01-01-00:00") +print(res4) # >>> 1 +# STEP_END +# REMOVE_START +assert res4 == 1 +# REMOVE_END diff --git a/doctests/dt_bloom.py b/doctests/dt_bloom.py new file mode 100644 index 0000000000..fab815240f --- /dev/null +++ b/doctests/dt_bloom.py @@ -0,0 +1,41 @@ +# EXAMPLE: bf_tutorial +# HIDE_START +""" +Code samples for Bloom filter doc pages: + https://redis.io/docs/latest/develop/data-types/probabilistic/bloom-filter/ +""" +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# STEP_START bloom +res1 = r.bf().reserve("bikes:models", 0.01, 1000) +print(res1) # >>> True + +res2 = r.bf().add("bikes:models", "Smoky Mountain Striker") +print(res2) # >>> True + +res3 = r.bf().exists("bikes:models", "Smoky Mountain Striker") +print(res3) # >>> True + +res4 = r.bf().madd( + "bikes:models", + "Rocky Mountain Racer", + "Cloudy City Cruiser", + "Windy City Wippet", +) +print(res4) # >>> [True, True, True] + +res5 = r.bf().mexists( + "bikes:models", + "Rocky Mountain Racer", + "Cloudy City Cruiser", + "Windy City Wippet", +) +print(res5) # >>> [True, True, True] +# STEP_END + +# REMOVE_START +assert res1 is True +# REMOVE_END diff --git a/doctests/dt_cms.py b/doctests/dt_cms.py new file mode 100644 index 0000000000..b784aa5f3b --- /dev/null +++ b/doctests/dt_cms.py @@ -0,0 +1,32 @@ +# EXAMPLE: cms_tutorial +# HIDE_START +""" +Code samples for Count-min sketch doc pages: + https://redis.io/docs/latest/develop/data-types/probabilistic/count-min-sketch/ +""" +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END +# REMOVE_START +r.delete("bikes:profit") +# REMOVE_END + +# STEP_START cms +res1 = r.cms().initbyprob("bikes:profit", 0.001, 0.002) +print(res1) # >>> True + +res2 = r.cms().incrby("bikes:profit", ["Smoky Mountain Striker"], [100]) +print(res2) # >>> [100] + +res3 = r.cms().incrby( + "bikes:profit", ["Rocky Mountain Racer", "Cloudy City Cruiser"], [200, 150] +) +print(res3) # >>> [200, 150] + +res4 = r.cms().query("bikes:profit", "Smoky Mountain Striker") +print(res4) # >>> [100] + +res5 = r.cms().info("bikes:profit") +print(res5.width, res5.depth, res5.count) # >>> 2000 9 450 +# STEP_END diff --git a/doctests/dt_cuckoo.py b/doctests/dt_cuckoo.py new file mode 100644 index 0000000000..b7c58e99f5 --- /dev/null +++ b/doctests/dt_cuckoo.py @@ -0,0 +1,36 @@ +# EXAMPLE: cuckoo_tutorial +# HIDE_START +""" +Code samples for Cuckoo filter doc pages: + https://redis.io/docs/latest/develop/data-types/probabilistic/cuckoo-filter/ +""" +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# REMOVE_START +r.delete("bikes:models") +# REMOVE_END + +# STEP_START cuckoo +res1 = r.cf().reserve("bikes:models", 1000000) +print(res1) # >>> True + +res2 = r.cf().add("bikes:models", "Smoky Mountain Striker") +print(res2) # >>> 1 + +res3 = r.cf().exists("bikes:models", "Smoky Mountain Striker") +print(res3) # >>> 1 + +res4 = r.cf().exists("bikes:models", "Terrible Bike Name") +print(res4) # >>> 0 + +res5 = r.cf().delete("bikes:models", "Smoky Mountain Striker") +print(res5) # >>> 1 +# STEP_END + +# REMOVE_START +assert res1 is True +assert res5 == 1 +# REMOVE_END diff --git a/doctests/dt_geo.py b/doctests/dt_geo.py new file mode 100644 index 0000000000..a841cb1df6 --- /dev/null +++ b/doctests/dt_geo.py @@ -0,0 +1,45 @@ +# EXAMPLE: geo_tutorial +# HIDE_START +""" +Code samples for Geospatial doc pages: + https://redis.io/docs/latest/develop/data-types/geospatial/ +""" +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END +# REMOVE_START +r.delete("bikes:rentable") +# REMOVE_END + +# STEP_START geoadd +res1 = r.geoadd("bikes:rentable", [-122.27652, 37.805186, "station:1"]) +print(res1) # >>> 1 + +res2 = r.geoadd("bikes:rentable", [-122.2674626, 37.8062344, "station:2"]) +print(res2) # >>> 1 + +res3 = r.geoadd("bikes:rentable", [-122.2469854, 37.8104049, "station:3"]) +print(res3) # >>> 1 +# STEP_END + +# REMOVE_START +assert res1 == 1 +assert res2 == 1 +assert res3 == 1 +# REMOVE_END + +# STEP_START geosearch +res4 = r.geosearch( + "bikes:rentable", + longitude=-122.27652, + latitude=37.805186, + radius=5, + unit="km", +) +print(res4) # >>> ['station:1', 'station:2', 'station:3'] +# STEP_END + +# REMOVE_START +assert res4 == ["station:1", "station:2", "station:3"] +# REMOVE_END diff --git a/doctests/dt_hash.py b/doctests/dt_hash.py new file mode 100644 index 0000000000..a9d5b0bf64 --- /dev/null +++ b/doctests/dt_hash.py @@ -0,0 +1,107 @@ +# EXAMPLE: hash_tutorial +# HIDE_START +""" +Code samples for Hash doc pages: + https://redis.io/docs/latest/develop/data-types/hashes/ +""" +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END +# STEP_START set_get_all +res1 = r.hset( + "bike:1", + mapping={ + "model": "Deimos", + "brand": "Ergonom", + "type": "Enduro bikes", + "price": 4972, + }, +) +print(res1) +# >>> 4 + +res2 = r.hget("bike:1", "model") +print(res2) +# >>> 'Deimos' + +res3 = r.hget("bike:1", "price") +print(res3) +# >>> '4972' + +res4 = r.hgetall("bike:1") +print(res4) +# >>> {'model': 'Deimos', 'brand': 'Ergonom', 'type': 'Enduro bikes', 'price': '4972'} + +# STEP_END + +# REMOVE_START +assert res1 == 4 +assert res2 == "Deimos" +assert res3 == "4972" +assert res4 == { + "model": "Deimos", + "brand": "Ergonom", + "type": "Enduro bikes", + "price": "4972", +} +# REMOVE_END + +# STEP_START hmget +res5 = r.hmget("bike:1", ["model", "price"]) +print(res5) +# >>> ['Deimos', '4972'] +# STEP_END + +# REMOVE_START +assert res5 == ["Deimos", "4972"] +# REMOVE_END + +# STEP_START hincrby +res6 = r.hincrby("bike:1", "price", 100) +print(res6) +# >>> 5072 +res7 = r.hincrby("bike:1", "price", -100) +print(res7) +# >>> 4972 +# STEP_END + +# REMOVE_START +assert res6 == 5072 +assert res7 == 4972 +# REMOVE_END + + +# STEP_START incrby_get_mget +res11 = r.hincrby("bike:1:stats", "rides", 1) +print(res11) +# >>> 1 +res12 = r.hincrby("bike:1:stats", "rides", 1) +print(res12) +# >>> 2 +res13 = r.hincrby("bike:1:stats", "rides", 1) +print(res13) +# >>> 3 +res14 = r.hincrby("bike:1:stats", "crashes", 1) +print(res14) +# >>> 1 +res15 = r.hincrby("bike:1:stats", "owners", 1) +print(res15) +# >>> 1 +res16 = r.hget("bike:1:stats", "rides") +print(res16) +# >>> 3 +res17 = r.hmget("bike:1:stats", ["crashes", "owners"]) +print(res17) +# >>> ['1', '1'] +# STEP_END + +# REMOVE_START +assert res11 == 1 +assert res12 == 2 +assert res13 == 3 +assert res14 == 1 +assert res15 == 1 +assert res16 == "3" +assert res17 == ["1", "1"] +# REMOVE_END diff --git a/doctests/dt_hll.py b/doctests/dt_hll.py new file mode 100644 index 0000000000..281d7f0a0b --- /dev/null +++ b/doctests/dt_hll.py @@ -0,0 +1,36 @@ +# # EXAMPLE: hll_tutorial +# HIDE_START +""" +Code samples for HyperLogLog doc pages: + https://redis.io/docs/latest/develop/data-types/probabilistic/hyperloglogs/ +""" + +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# REMOVE_START +r.delete("bikes", "commuter_bikes", "all_bikes") +# REMOVE_END + +# STEP_START pfadd +res1 = r.pfadd("bikes", "Hyperion", "Deimos", "Phoebe", "Quaoar") +print(res1) # >>> 1 + +res2 = r.pfcount("bikes") +print(res2) # >>> 4 + +res3 = r.pfadd("commuter_bikes", "Salacia", "Mimas", "Quaoar") +print(res3) # >>> 1 + +res4 = r.pfmerge("all_bikes", "bikes", "commuter_bikes") +print(res4) # >>> True + +res5 = r.pfcount("all_bikes") +print(res5) # >>> 6 +# STEP_END + +# REMOVE_START +assert res4 is True +# REMOVE_END diff --git a/doctests/dt_json.py b/doctests/dt_json.py new file mode 100644 index 0000000000..f7c2226048 --- /dev/null +++ b/doctests/dt_json.py @@ -0,0 +1,372 @@ +# EXAMPLE: json_tutorial +# HIDE_START +""" +Code samples for JSON doc pages: + https://redis.io/docs/latest/develop/data-types/json/ +""" +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# REMOVE_START +r.delete("bike") +r.delete("crashes") +r.delete("newbike") +r.delete("rider") +# REMOVE_END + +# STEP_START set_get +res1 = r.json().set("bike", "$", '"Hyperion"') +print(res1) # >>> True + +res2 = r.json().get("bike", "$") +print(res2) # >>> ['"Hyperion"'] + +res3 = r.json().type("bike", "$") +print(res3) # >>> ['string'] +# STEP_END + +# REMOVE_START +assert res2 == ['"Hyperion"'] +# REMOVE_END + +# STEP_START str +res4 = r.json().strlen("bike", "$") +print(res4) # >>> [10] + +res5 = r.json().strappend("bike", '" (Enduro bikes)"') +print(res5) # >>> 27 + +res6 = r.json().get("bike", "$") +print(res6) # >>> ['"Hyperion"" (Enduro bikes)"'] +# STEP_END + +# REMOVE_START +assert res6 == ['"Hyperion"" (Enduro bikes)"'] +# REMOVE_END + +# STEP_START num +res7 = r.json().set("crashes", "$", 0) +print(res7) # >>> True + +res8 = r.json().numincrby("crashes", "$", 1) +print(res8) # >>> [1] + +res9 = r.json().numincrby("crashes", "$", 1.5) +print(res9) # >>> [2.5] + +res10 = r.json().numincrby("crashes", "$", -0.75) +print(res10) # >>> [1.75] +# STEP_END + +# REMOVE_START +assert res10 == [1.75] +# REMOVE_END + +# STEP_START arr +res11 = r.json().set("newbike", "$", ["Deimos", {"crashes": 0}, None]) +print(res11) # >>> True + +res12 = r.json().get("newbike", "$") +print(res12) # >>> ['["Deimos", { "crashes": 0 }, null]'] + +res13 = r.json().get("newbike", "$[1].crashes") +print(res13) # >>> ['0'] + +res14 = r.json().delete("newbike", "$.[-1]") +print(res14) # >>> [1] + +res15 = r.json().get("newbike", "$") +print(res15) # >>> [['Deimos', {'crashes': 0}]] +# STEP_END + +# REMOVE_START +assert res15 == [["Deimos", {"crashes": 0}]] +# REMOVE_END + +# STEP_START arr2 +res16 = r.json().set("riders", "$", []) +print(res16) # >>> True + +res17 = r.json().arrappend("riders", "$", "Norem") +print(res17) # >>> [1] + +res18 = r.json().get("riders", "$") +print(res18) # >>> [['Norem']] + +res19 = r.json().arrinsert("riders", "$", 1, "Prickett", "Royce", "Castilla") +print(res19) # >>> [4] + +res20 = r.json().get("riders", "$") +print(res20) # >>> [['Norem', 'Prickett', 'Royce', 'Castilla']] + +res21 = r.json().arrtrim("riders", "$", 1, 1) +print(res21) # >>> [1] + +res22 = r.json().get("riders", "$") +print(res22) # >>> [['Prickett']] + +res23 = r.json().arrpop("riders", "$") +print(res23) # >>> ['"Prickett"'] + +res24 = r.json().arrpop("riders", "$") +print(res24) # >>> [None] +# STEP_END + +# REMOVE_START +assert res24 == [None] +# REMOVE_END + +# STEP_START obj +res25 = r.json().set( + "bike:1", "$", {"model": "Deimos", "brand": "Ergonom", "price": 4972} +) +print(res25) # >>> True + +res26 = r.json().objlen("bike:1", "$") +print(res26) # >>> [3] + +res27 = r.json().objkeys("bike:1", "$") +print(res27) # >>> [['model', 'brand', 'price']] +# STEP_END + +# REMOVE_START +assert res27 == [["model", "brand", "price"]] +# REMOVE_END + +# STEP_START set_bikes +# HIDE_START +inventory_json = { + "inventory": { + "mountain_bikes": [ + { + "id": "bike:1", + "model": "Phoebe", + "description": "This is a mid-travel trail slayer that is a fantastic " + "daily driver or one bike quiver. The Shimano Claris 8-speed groupset " + "gives plenty of gear range to tackle hills and there\u2019s room for " + "mudguards and a rack too. This is the bike for the rider who wants " + "trail manners with low fuss ownership.", + "price": 1920, + "specs": {"material": "carbon", "weight": 13.1}, + "colors": ["black", "silver"], + }, + { + "id": "bike:2", + "model": "Quaoar", + "description": "Redesigned for the 2020 model year, this bike " + "impressed our testers and is the best all-around trail bike we've " + "ever tested. The Shimano gear system effectively does away with an " + "external cassette, so is super low maintenance in terms of wear " + "and tear. All in all it's an impressive package for the price, " + "making it very competitive.", + "price": 2072, + "specs": {"material": "aluminium", "weight": 7.9}, + "colors": ["black", "white"], + }, + { + "id": "bike:3", + "model": "Weywot", + "description": "This bike 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. A set of " + "powerful Shimano hydraulic disc brakes provide ample stopping " + "ability. If you're after a budget option, this is one of the best " + "bikes you could get.", + "price": 3264, + "specs": {"material": "alloy", "weight": 13.8}, + }, + ], + "commuter_bikes": [ + { + "id": "bike:4", + "model": "Salacia", + "description": "This bike is a great option for anyone who just " + "wants a bike to get about on With a slick-shifting Claris gears " + "from Shimano\u2019s, this is a bike which doesn\u2019t break the " + "bank and delivers craved performance. It\u2019s for the rider " + "who wants both efficiency and capability.", + "price": 1475, + "specs": {"material": "aluminium", "weight": 16.6}, + "colors": ["black", "silver"], + }, + { + "id": "bike:5", + "model": "Mimas", + "description": "A real joy to ride, this bike got very high " + "scores in last years Bike of the year report. The carefully " + "crafted 50-34 tooth chainset and 11-32 tooth cassette give an " + "easy-on-the-legs bottom gear for climbing, and the high-quality " + "Vittoria Zaffiro tires give balance and grip.It includes " + "a low-step frame , our memory foam seat, bump-resistant shocks and " + "conveniently placed thumb throttle. Put it all together and you " + "get a bike that helps redefine what can be done for this price.", + "price": 3941, + "specs": {"material": "alloy", "weight": 11.6}, + }, + ], + } +} +# HIDE_END + +res1 = r.json().set("bikes:inventory", "$", inventory_json) +print(res1) # >>> True +# STEP_END + +# STEP_START get_bikes +res2 = r.json().get("bikes:inventory", "$.inventory.*") +print(res2) +# >>> [[{'id': 'bike:1', 'model': 'Phoebe', +# >>> 'description': 'This is a mid-travel trail slayer... +# STEP_END + +# STEP_START get_mtnbikes +res3 = r.json().get("bikes:inventory", "$.inventory.mountain_bikes[*].model") +print(res3) # >>> [['Phoebe', 'Quaoar', 'Weywot']] + +res4 = r.json().get("bikes:inventory", '$.inventory["mountain_bikes"][*].model') +print(res4) # >>> [['Phoebe', 'Quaoar', 'Weywot']] + +res5 = r.json().get("bikes:inventory", "$..mountain_bikes[*].model") +print(res5) # >>> [['Phoebe', 'Quaoar', 'Weywot']] +# STEP_END + +# REMOVE_START +assert res3 == ["Phoebe", "Quaoar", "Weywot"] +assert res4 == ["Phoebe", "Quaoar", "Weywot"] +assert res5 == ["Phoebe", "Quaoar", "Weywot"] +# REMOVE_END + +# STEP_START get_models +res6 = r.json().get("bikes:inventory", "$..model") +print(res6) # >>> [['Phoebe', 'Quaoar', 'Weywot', 'Salacia', 'Mimas']] +# STEP_END + +# REMOVE_START +assert res6 == ["Phoebe", "Quaoar", "Weywot", "Salacia", "Mimas"] +# REMOVE_END + +# STEP_START get2mtnbikes +res7 = r.json().get("bikes:inventory", "$..mountain_bikes[0:2].model") +print(res7) # >>> [['Phoebe', 'Quaoar']] +# STEP_END + +# REMOVE_START +assert res7 == ["Phoebe", "Quaoar"] +# REMOVE_END + +# STEP_START filter1 +res8 = r.json().get( + "bikes:inventory", + "$..mountain_bikes[?(@.price < 3000 && @.specs.weight < 10)]", +) +print(res8) +# >>> [{'id': 'bike:2', 'model': 'Quaoar', +# 'description': "Redesigned for the 2020 model year... +# STEP_END + +# REMOVE_START +assert res8 == [ + { + "id": "bike:2", + "model": "Quaoar", + "description": "Redesigned for the 2020 model year, this bike impressed " + "our testers and is the best all-around trail bike we've ever tested. " + "The Shimano gear system effectively does away with an external cassette, " + "so is super low maintenance in terms of wear and tear. All in all it's " + "an impressive package for the price, making it very competitive.", + "price": 2072, + "specs": {"material": "aluminium", "weight": 7.9}, + "colors": ["black", "white"], + } +] +# REMOVE_END + +# STEP_START filter2 +# names of bikes made from an alloy +res9 = r.json().get("bikes:inventory", "$..[?(@.specs.material == 'alloy')].model") +print(res9) # >>> ['Weywot', 'Mimas'] +# STEP_END + +# REMOVE_START +assert res9 == ["Weywot", "Mimas"] +# REMOVE_END + +# STEP_START filter3 +res10 = r.json().get("bikes:inventory", "$..[?(@.specs.material =~ '(?i)al')].model") +print(res10) # >>> ['Quaoar', 'Weywot', 'Salacia', 'Mimas'] +# STEP_END + +# REMOVE_START +assert res10 == ["Quaoar", "Weywot", "Salacia", "Mimas"] +# REMOVE_END + +# STEP_START filter4 +res11 = r.json().set( + "bikes:inventory", "$.inventory.mountain_bikes[0].regex_pat", "(?i)al" +) +res12 = r.json().set( + "bikes:inventory", "$.inventory.mountain_bikes[1].regex_pat", "(?i)al" +) +res13 = r.json().set( + "bikes:inventory", "$.inventory.mountain_bikes[2].regex_pat", "(?i)al" +) + +res14 = r.json().get( + "bikes:inventory", + "$.inventory.mountain_bikes[?(@.specs.material =~ @.regex_pat)].model", +) +print(res14) # >>> ['Quaoar', 'Weywot'] +# STEP_END + +# REMOVE_START +assert res14 == ["Quaoar", "Weywot"] +# REMOVE_END + +# STEP_START update_bikes +res15 = r.json().get("bikes:inventory", "$..price") +print(res15) # >>> [1920, 2072, 3264, 1475, 3941] + +res16 = r.json().numincrby("bikes:inventory", "$..price", -100) +print(res16) # >>> [1820, 1972, 3164, 1375, 3841] + +res17 = r.json().numincrby("bikes:inventory", "$..price", 100) +print(res17) # >>> [1920, 2072, 3264, 1475, 3941] +# STEP_END + +# REMOVE_START +assert res15 == [1920, 2072, 3264, 1475, 3941] +assert res16 == [1820, 1972, 3164, 1375, 3841] +assert res17 == [1920, 2072, 3264, 1475, 3941] +# REMOVE_END + +# STEP_START update_filters1 +res18 = r.json().set("bikes:inventory", "$.inventory.*[?(@.price<2000)].price", 1500) +res19 = r.json().get("bikes:inventory", "$..price") +print(res19) # >>> [1500, 2072, 3264, 1500, 3941] +# STEP_END + +# REMOVE_START +assert res19 == [1500, 2072, 3264, 1500, 3941] +# REMOVE_END + +# STEP_START update_filters2 +res20 = r.json().arrappend( + "bikes:inventory", "$.inventory.*[?(@.price<2000)].colors", "pink" +) +print(res20) # >>> [3, 3] + +res21 = r.json().get("bikes:inventory", "$..[*].colors") +print( + res21 +) # >>> [['black', 'silver', 'pink'], ['black', 'white'], ['black', 'silver', 'pink']] +# STEP_END + +# REMOVE_START +assert res21 == [ + ["black", "silver", "pink"], + ["black", "white"], + ["black", "silver", "pink"], +] +# REMOVE_END diff --git a/doctests/dt_list.py b/doctests/dt_list.py new file mode 100644 index 0000000000..be8a4b8562 --- /dev/null +++ b/doctests/dt_list.py @@ -0,0 +1,323 @@ +# EXAMPLE: list_tutorial +# HIDE_START +""" +Code samples for List doc pages: + https://redis.io/docs/latest/develop/data-types/lists/ +""" + +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END +# REMOVE_START +r.delete("bikes:repairs") +r.delete("bikes:finished") +# REMOVE_END + +# STEP_START queue +res1 = r.lpush("bikes:repairs", "bike:1") +print(res1) # >>> 1 + +res2 = r.lpush("bikes:repairs", "bike:2") +print(res2) # >>> 2 + +res3 = r.rpop("bikes:repairs") +print(res3) # >>> bike:1 + +res4 = r.rpop("bikes:repairs") +print(res4) # >>> bike:2 +# STEP_END + +# REMOVE_START +assert res1 == 1 +assert res2 == 2 +assert res3 == "bike:1" +assert res4 == "bike:2" +# REMOVE_END + +# STEP_START stack +res5 = r.lpush("bikes:repairs", "bike:1") +print(res5) # >>> 1 + +res6 = r.lpush("bikes:repairs", "bike:2") +print(res6) # >>> 2 + +res7 = r.lpop("bikes:repairs") +print(res7) # >>> bike:2 + +res8 = r.lpop("bikes:repairs") +print(res8) # >>> bike:1 +# STEP_END + +# REMOVE_START +assert res5 == 1 +assert res6 == 2 +assert res7 == "bike:2" +assert res8 == "bike:1" +# REMOVE_END + +# STEP_START llen +res9 = r.llen("bikes:repairs") +print(res9) # >>> 0 +# STEP_END + +# REMOVE_START +assert res9 == 0 +# REMOVE_END + +# STEP_START lmove_lrange +res10 = r.lpush("bikes:repairs", "bike:1") +print(res10) # >>> 1 + +res11 = r.lpush("bikes:repairs", "bike:2") +print(res11) # >>> 2 + +res12 = r.lmove("bikes:repairs", "bikes:finished", "LEFT", "LEFT") +print(res12) # >>> 'bike:2' + +res13 = r.lrange("bikes:repairs", 0, -1) +print(res13) # >>> ['bike:1'] + +res14 = r.lrange("bikes:finished", 0, -1) +print(res14) # >>> ['bike:2'] +# STEP_END + +# REMOVE_START +assert res10 == 1 +assert res11 == 2 +assert res12 == "bike:2" +assert res13 == ["bike:1"] +assert res14 == ["bike:2"] +r.delete("bikes:repairs") +# REMOVE_END + +# STEP_START lpush_rpush +res15 = r.rpush("bikes:repairs", "bike:1") +print(res15) # >>> 1 + +res16 = r.rpush("bikes:repairs", "bike:2") +print(res16) # >>> 2 + +res17 = r.lpush("bikes:repairs", "bike:important_bike") +print(res17) # >>> 3 + +res18 = r.lrange("bikes:repairs", 0, -1) +print(res18) # >>> ['bike:important_bike', 'bike:1', 'bike:2'] +# STEP_END + +# REMOVE_START +assert res15 == 1 +assert res16 == 2 +assert res17 == 3 +assert res18 == ["bike:important_bike", "bike:1", "bike:2"] +r.delete("bikes:repairs") +# REMOVE_END + +# STEP_START variadic +res19 = r.rpush("bikes:repairs", "bike:1", "bike:2", "bike:3") +print(res19) # >>> 3 + +res20 = r.lpush("bikes:repairs", "bike:important_bike", "bike:very_important_bike") +print(res20) # >>> 5 + +res21 = r.lrange("bikes:repairs", 0, -1) +print( + res21 +) # >>> ['bike:very_important_bike', 'bike:important_bike', 'bike:1', ... +# STEP_END + +# REMOVE_START +assert res19 == 3 +assert res20 == 5 +assert res21 == [ + "bike:very_important_bike", + "bike:important_bike", + "bike:1", + "bike:2", + "bike:3", +] +r.delete("bikes:repairs") +# REMOVE_END + +# STEP_START lpop_rpop +res22 = r.rpush("bikes:repairs", "bike:1", "bike:2", "bike:3") +print(res22) # >>> 3 + +res23 = r.rpop("bikes:repairs") +print(res23) # >>> 'bike:3' + +res24 = r.lpop("bikes:repairs") +print(res24) # >>> 'bike:1' + +res25 = r.rpop("bikes:repairs") +print(res25) # >>> 'bike:2' + +res26 = r.rpop("bikes:repairs") +print(res26) # >>> None +# STEP_END + +# REMOVE_START +assert res22 == 3 +assert res23 == "bike:3" +assert res24 == "bike:1" +assert res25 == "bike:2" +assert res26 is None +# REMOVE_END + +# STEP_START ltrim +res27 = r.lpush("bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5") +print(res27) # >>> 5 + +res28 = r.ltrim("bikes:repairs", 0, 2) +print(res28) # >>> True + +res29 = r.lrange("bikes:repairs", 0, -1) +print(res29) # >>> ['bike:5', 'bike:4', 'bike:3'] +# STEP_END + +# REMOVE_START +assert res27 == 5 +assert res28 is True +assert res29 == ["bike:5", "bike:4", "bike:3"] +r.delete("bikes:repairs") +# REMOVE_END + +# STEP_START ltrim_end_of_list +res27 = r.rpush("bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5") +print(res27) # >>> 5 + +res28 = r.ltrim("bikes:repairs", -3, -1) +print(res28) # >>> True + +res29 = r.lrange("bikes:repairs", 0, -1) +print(res29) # >>> ['bike:3', 'bike:4', 'bike:5'] +# STEP_END + +# REMOVE_START +assert res27 == 5 +assert res28 is True +assert res29 == ["bike:3", "bike:4", "bike:5"] +r.delete("bikes:repairs") +# REMOVE_END + +# STEP_START brpop +res31 = r.rpush("bikes:repairs", "bike:1", "bike:2") +print(res31) # >>> 2 + +res32 = r.brpop("bikes:repairs", timeout=1) +print(res32) # >>> ('bikes:repairs', 'bike:2') + +res33 = r.brpop("bikes:repairs", timeout=1) +print(res33) # >>> ('bikes:repairs', 'bike:1') + +res34 = r.brpop("bikes:repairs", timeout=1) +print(res34) # >>> None +# STEP_END + +# REMOVE_START +assert res31 == 2 +assert res32 == ("bikes:repairs", "bike:2") +assert res33 == ("bikes:repairs", "bike:1") +assert res34 is None +r.delete("bikes:repairs") +r.delete("new_bikes") +# REMOVE_END + +# STEP_START rule_1 +res35 = r.delete("new_bikes") +print(res35) # >>> 0 + +res36 = r.lpush("new_bikes", "bike:1", "bike:2", "bike:3") +print(res36) # >>> 3 +# STEP_END + +# REMOVE_START +assert res35 == 0 +assert res36 == 3 +r.delete("new_bikes") +# REMOVE_END + +# STEP_START rule_1.1 +res37 = r.set("new_bikes", "bike:1") +print(res37) # >>> True + +res38 = r.type("new_bikes") +print(res38) # >>> 'string' + +try: + res39 = r.lpush("new_bikes", "bike:2", "bike:3") + # >>> redis.exceptions.ResponseError: + # >>> WRONGTYPE Operation against a key holding the wrong kind of value +except redis.exceptions.ResponseError as e: + print(e) +# STEP_END + +# REMOVE_START +assert res37 is True +assert res38 == "string" +r.delete("new_bikes") +# REMOVE_END + +# STEP_START rule_2 +r.lpush("bikes:repairs", "bike:1", "bike:2", "bike:3") +print(res36) # >>> 3 + +res40 = r.exists("bikes:repairs") +print(res40) # >>> 1 + +res41 = r.lpop("bikes:repairs") +print(res41) # >>> 'bike:3' + +res42 = r.lpop("bikes:repairs") +print(res42) # >>> 'bike:2' + +res43 = r.lpop("bikes:repairs") +print(res43) # >>> 'bike:1' + +res44 = r.exists("bikes:repairs") +print(res44) # >>> False +# STEP_END + +# REMOVE_START +assert res40 == 1 +assert res41 == "bike:3" +assert res42 == "bike:2" +assert res43 == "bike:1" +assert res44 == 0 +r.delete("bikes:repairs") +# REMOVE_END + +# STEP_START rule_3 +res45 = r.delete("bikes:repairs") +print(res45) # >>> 0 + +res46 = r.llen("bikes:repairs") +print(res46) # >>> 0 + +res47 = r.lpop("bikes:repairs") +print(res47) # >>> None +# STEP_END + +# REMOVE_START +assert res45 == 0 +assert res46 == 0 +assert res47 is None +# REMOVE_END + +# STEP_START ltrim.1 +res48 = r.lpush("bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5") +print(res48) # >>> 5 + +res49 = r.ltrim("bikes:repairs", 0, 2) +print(res49) # >>> True + +res50 = r.lrange("bikes:repairs", 0, -1) +print(res50) # >>> ['bike:5', 'bike:4', 'bike:3'] +# STEP_END + +# REMOVE_START +assert res48 == 5 +assert res49 is True +assert res50 == ["bike:5", "bike:4", "bike:3"] +r.delete("bikes:repairs") +# REMOVE_END diff --git a/doctests/dt_set.py b/doctests/dt_set.py new file mode 100644 index 0000000000..bcc7ba934f --- /dev/null +++ b/doctests/dt_set.py @@ -0,0 +1,172 @@ +# EXAMPLE: sets_tutorial +# HIDE_START +""" +Code samples for Set doc pages: + https://redis.io/docs/latest/develop/data-types/sets/ +""" + +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END +# REMOVE_START +r.delete("bikes:racing:france") +r.delete("bikes:racing:usa") +# REMOVE_END + +# STEP_START sadd +res1 = r.sadd("bikes:racing:france", "bike:1") +print(res1) # >>> 1 + +res2 = r.sadd("bikes:racing:france", "bike:1") +print(res2) # >>> 0 + +res3 = r.sadd("bikes:racing:france", "bike:2", "bike:3") +print(res3) # >>> 2 + +res4 = r.sadd("bikes:racing:usa", "bike:1", "bike:4") +print(res4) # >>> 2 +# STEP_END + +# REMOVE_START +assert res1 == 1 +assert res2 == 0 +assert res3 == 2 +assert res4 == 2 +# REMOVE_END + +# STEP_START sismember +# HIDE_START +r.sadd("bikes:racing:france", "bike:1", "bike:2", "bike:3") +r.sadd("bikes:racing:usa", "bike:1", "bike:4") +# HIDE_END +res5 = r.sismember("bikes:racing:usa", "bike:1") +print(res5) # >>> 1 + +res6 = r.sismember("bikes:racing:usa", "bike:2") +print(res6) # >>> 1 +# STEP_END + +# REMOVE_START +assert res5 == 1 +assert res6 == 0 +# REMOVE_END + +# STEP_START sinter +# HIDE_START +r.sadd("bikes:racing:france", "bike:1", "bike:2", "bike:3") +r.sadd("bikes:racing:usa", "bike:1", "bike:4") +# HIDE_END +res7 = r.sinter("bikes:racing:france", "bikes:racing:usa") +print(res7) # >>> {'bike:1'} +# STEP_END + +# REMOVE_START +assert res7 == {"bike:1"} +# REMOVE_END + +# STEP_START scard +# HIDE_START +r.sadd("bikes:racing:france", "bike:1", "bike:2", "bike:3") +# HIDE_END +res8 = r.scard("bikes:racing:france") +print(res8) # >>> 3 +# STEP_END + +# REMOVE_START +assert res8 == 3 +r.delete("bikes:racing:france") +# REMOVE_END + +# STEP_START sadd_smembers +res9 = r.sadd("bikes:racing:france", "bike:1", "bike:2", "bike:3") +print(res9) # >>> 3 + +res10 = r.smembers("bikes:racing:france") +print(res10) # >>> {'bike:1', 'bike:2', 'bike:3'} +# STEP_END + +# REMOVE_START +assert res9 == 3 +assert res10 == {"bike:1", "bike:2", "bike:3"} +# REMOVE_END + +# STEP_START smismember +res11 = r.sismember("bikes:racing:france", "bike:1") +print(res11) # >>> 1 + +res12 = r.smismember("bikes:racing:france", "bike:2", "bike:3", "bike:4") +print(res12) # >>> [1, 1, 0] +# STEP_END + +# REMOVE_START +assert res11 == 1 +assert res12 == [1, 1, 0] +# REMOVE_END + +# STEP_START sdiff +r.sadd("bikes:racing:france", "bike:1", "bike:2", "bike:3") +r.sadd("bikes:racing:usa", "bike:1", "bike:4") + +res13 = r.sdiff("bikes:racing:france", "bikes:racing:usa") +print(res13) # >>> {'bike:2', 'bike:3'} +# STEP_END + +# REMOVE_START +assert res13 == {"bike:2", "bike:3"} +r.delete("bikes:racing:france") +r.delete("bikes:racing:usa") +# REMOVE_END + +# STEP_START multisets +r.sadd("bikes:racing:france", "bike:1", "bike:2", "bike:3") +r.sadd("bikes:racing:usa", "bike:1", "bike:4") +r.sadd("bikes:racing:italy", "bike:1", "bike:2", "bike:3", "bike:4") + +res13 = r.sinter("bikes:racing:france", "bikes:racing:usa", "bikes:racing:italy") +print(res13) # >>> {'bike:1'} + +res14 = r.sunion("bikes:racing:france", "bikes:racing:usa", "bikes:racing:italy") +print(res14) # >>> {'bike:1', 'bike:2', 'bike:3', 'bike:4'} + +res15 = r.sdiff("bikes:racing:france", "bikes:racing:usa", "bikes:racing:italy") +print(res15) # >>> set() + +res16 = r.sdiff("bikes:racing:usa", "bikes:racing:france") +print(res16) # >>> {'bike:4'} + +res17 = r.sdiff("bikes:racing:france", "bikes:racing:usa") +print(res17) # >>> {'bike:2', 'bike:3'} +# STEP_END + +# REMOVE_START +assert res13 == {"bike:1"} +assert res14 == {"bike:1", "bike:2", "bike:3", "bike:4"} +assert res15 == set() +assert res16 == {"bike:4"} +assert res17 == {"bike:2", "bike:3"} +r.delete("bikes:racing:france") +r.delete("bikes:racing:usa") +r.delete("bikes:racing:italy") +# REMOVE_END + +# STEP_START srem +r.sadd("bikes:racing:france", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5") + +res18 = r.srem("bikes:racing:france", "bike:1") +print(res18) # >>> 1 + +res19 = r.spop("bikes:racing:france") +print(res19) # >>> bike:3 + +res20 = r.smembers("bikes:racing:france") +print(res20) # >>> {'bike:2', 'bike:4', 'bike:5'} + +res21 = r.srandmember("bikes:racing:france") +print(res21) # >>> bike:4 +# STEP_END + +# REMOVE_START +assert res18 == 1 +# none of the other results are deterministic +# REMOVE_END diff --git a/doctests/dt_ss.py b/doctests/dt_ss.py new file mode 100644 index 0000000000..76fa72ad4c --- /dev/null +++ b/doctests/dt_ss.py @@ -0,0 +1,118 @@ +# EXAMPLE: ss_tutorial +# HIDE_START +""" +Code samples for Sorted set doc pages: + https://redis.io/docs/latest/develop/data-types/sorted-sets/ +""" + +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# REMOVE_START +r.delete("racer_scores") +# REMOVE_END + +# STEP_START zadd +res1 = r.zadd("racer_scores", {"Norem": 10}) +print(res1) # >>> 1 + +res2 = r.zadd("racer_scores", {"Castilla": 12}) +print(res2) # >>> 1 + +res3 = r.zadd( + "racer_scores", + {"Sam-Bodden": 8, "Royce": 10, "Ford": 6, "Prickett": 14, "Castilla": 12}, +) +print(res3) # >>> 4 +# STEP_END + +# REMOVE_START +assert r.zcard("racer_scores") == 6 +# REMOVE_END + +# STEP_START zrange +res4 = r.zrange("racer_scores", 0, -1) +print(res4) # >>> ['Ford', 'Sam-Bodden', 'Norem', 'Royce', 'Castilla', 'Prickett'] + +res5 = r.zrevrange("racer_scores", 0, -1) +print(res5) # >>> ['Prickett', 'Castilla', 'Royce', 'Norem', 'Sam-Bodden', 'Ford'] +# STEP_END + +# STEP_START zrange_withscores +res6 = r.zrange("racer_scores", 0, -1, withscores=True) +print( + res6 +) +# >>> [ +# ('Ford', 6.0), ('Sam-Bodden', 8.0), ('Norem', 10.0), ('Royce', 10.0), +# ('Castilla', 12.0), ('Prickett', 14.0) +# ] +# STEP_END + +# STEP_START zrangebyscore +res7 = r.zrangebyscore("racer_scores", "-inf", 10) +print(res7) # >>> ['Ford', 'Sam-Bodden', 'Norem', 'Royce'] +# STEP_END + +# STEP_START zremrangebyscore +res8 = r.zrem("racer_scores", "Castilla") +print(res8) # >>> 1 + +res9 = r.zremrangebyscore("racer_scores", "-inf", 9) +print(res9) # >>> 2 + +res10 = r.zrange("racer_scores", 0, -1) +print(res10) # >>> ['Norem', 'Royce', 'Prickett'] +# STEP_END + +# REMOVE_START +assert r.zcard("racer_scores") == 3 +# REMOVE_END + +# STEP_START zrank +res11 = r.zrank("racer_scores", "Norem") +print(res11) # >>> 0 + +res12 = r.zrevrank("racer_scores", "Norem") +print(res12) # >>> 2 +# STEP_END + +# STEP_START zadd_lex +res13 = r.zadd( + "racer_scores", + { + "Norem": 0, + "Sam-Bodden": 0, + "Royce": 0, + "Ford": 0, + "Prickett": 0, + "Castilla": 0, + }, +) +print(res13) # >>> 3 + +res14 = r.zrange("racer_scores", 0, -1) +print(res14) # >>> ['Castilla', 'Ford', 'Norem', 'Prickett', 'Royce', 'Sam-Bodden'] + +res15 = r.zrangebylex("racer_scores", "[A", "[L") +print(res15) # >>> ['Castilla', 'Ford'] +# STEP_END + +# STEP_START leaderboard +res16 = r.zadd("racer_scores", {"Wood": 100}) +print(res16) # >>> 1 + +res17 = r.zadd("racer_scores", {"Henshaw": 100}) +print(res17) # >>> 1 + +res18 = r.zadd("racer_scores", {"Henshaw": 150}) +print(res18) # >>> 0 + +res19 = r.zincrby("racer_scores", 50, "Wood") +print(res19) # >>> 150.0 + +res20 = r.zincrby("racer_scores", 50, "Henshaw") +print(res20) # >>> 200.0 +# STEP_END diff --git a/doctests/dt_stream.py b/doctests/dt_stream.py new file mode 100644 index 0000000000..7282ee3d24 --- /dev/null +++ b/doctests/dt_stream.py @@ -0,0 +1,423 @@ +# EXAMPLE: stream_tutorial +# HIDE_START +""" +Code samples for Stream doc pages: + https://redis.io/docs/latest/develop/data-types/streams/ +""" + +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END +# REMOVE_START +r.delete("race:france", "race:italy", "race:usa") +# REMOVE_END + +# STEP_START xadd +res1 = r.xadd( + "race:france", + {"rider": "Castilla", "speed": 30.2, "position": 1, "location_id": 1}, +) +print(res1) # >>> 1692629576966-0 + +res2 = r.xadd( + "race:france", + {"rider": "Norem", "speed": 28.8, "position": 3, "location_id": 1}, +) +print(res2) # >>> 1692629594113-0 + +res3 = r.xadd( + "race:france", + {"rider": "Prickett", "speed": 29.7, "position": 2, "location_id": 1}, +) +print(res3) # >>> 1692629613374-0 +# STEP_END + +# REMOVE_START +assert r.xlen("race:france") == 3 +# REMOVE_END + +# STEP_START xrange +res4 = r.xrange("race:france", "1691765278160-0", "+", 2) +print( + res4 +) # >>> [ +# ('1692629576966-0', +# {'rider': 'Castilla', 'speed': '30.2', 'position': '1', 'location_id': '1'} +# ), +# ('1692629594113-0', +# {'rider': 'Norem', 'speed': '28.8', 'position': '3', 'location_id': '1'} +# ) +# ] +# STEP_END + +# STEP_START xread_block +res5 = r.xread(streams={"race:france": 0}, count=100, block=300) +print( + res5 +) +# >>> [ +# ['race:france', +# [('1692629576966-0', +# {'rider': 'Castilla', 'speed': '30.2', 'position': '1', 'location_id': '1'} +# ), +# ('1692629594113-0', +# {'rider': 'Norem', 'speed': '28.8', 'position': '3', 'location_id': '1'} +# ), +# ('1692629613374-0', +# {'rider': 'Prickett', 'speed': '29.7', 'position': '2', 'location_id': '1'} +# )] +# ] +# ] +# STEP_END + +# STEP_START xadd_2 +res6 = r.xadd( + "race:france", + {"rider": "Castilla", "speed": 29.9, "position": 1, "location_id": 2}, +) +print(res6) # >>> 1692629676124-0 +# STEP_END + +# STEP_START xlen +res7 = r.xlen("race:france") +print(res7) # >>> 4 +# STEP_END + + +# STEP_START xadd_id +res8 = r.xadd("race:usa", {"racer": "Castilla"}, id="0-1") +print(res8) # >>> 0-1 + +res9 = r.xadd("race:usa", {"racer": "Norem"}, id="0-2") +print(res9) # >>> 0-2 +# STEP_END + +# STEP_START xadd_bad_id +try: + res10 = r.xadd("race:usa", {"racer": "Prickett"}, id="0-1") + print(res10) # >>> 0-1 +except redis.exceptions.ResponseError as e: + print(e) # >>> WRONGID +# STEP_END + +# STEP_START xadd_7 +# Not yet implemented +# STEP_END + +# STEP_START xrange_all +res11 = r.xrange("race:france", "-", "+") +print( + res11 +) +# >>> [ +# ('1692629576966-0', +# {'rider': 'Castilla', 'speed': '30.2', 'position': '1', 'location_id': '1'} +# ), +# ('1692629594113-0', +# {'rider': 'Norem', 'speed': '28.8', 'position': '3', 'location_id': '1'} +# ), +# ('1692629613374-0', +# {'rider': 'Prickett', 'speed': '29.7', 'position': '2', 'location_id': '1'} +# ), +# ('1692629676124-0', +# {'rider': 'Castilla', 'speed': '29.9', 'position': '1', 'location_id': '2'} +# ) +# ] +# STEP_END + +# STEP_START xrange_time +res12 = r.xrange("race:france", 1692629576965, 1692629576967) +print( + res12 +) +# >>> [ +# ('1692629576966-0', +# {'rider': 'Castilla', 'speed': '30.2', 'position': '1', 'location_id': '1'} +# ) +# ] +# STEP_END + +# STEP_START xrange_step_1 +res13 = r.xrange("race:france", "-", "+", 2) +print( + res13 +) +# >>> [ +# ('1692629576966-0', +# {'rider': 'Castilla', 'speed': '30.2', 'position': '1', 'location_id': '1'} +# ), +# ('1692629594113-0', +# {'rider': 'Norem', 'speed': '28.8', 'position': '3', 'location_id': '1'} +# ) +# ] +# STEP_END + +# STEP_START xrange_step_2 +res14 = r.xrange("race:france", "(1692629594113-0", "+", 2) +print( + res14 +) +# >>> [ +# ('1692629613374-0', +# {'rider': 'Prickett', 'speed': '29.7', 'position': '2', 'location_id': '1'} +# ), +# ('1692629676124-0', +# {'rider': 'Castilla', 'speed': '29.9', 'position': '1', 'location_id': '2'} +# ) +# ] +# STEP_END + +# STEP_START xrange_empty +res15 = r.xrange("race:france", "(1692629676124-0", "+", 2) +print(res15) # >>> [] +# STEP_END + +# STEP_START xrevrange +res16 = r.xrevrange("race:france", "+", "-", 1) +print( + res16 +) +# >>> [ +# ('1692629676124-0', +# {'rider': 'Castilla', 'speed': '29.9', 'position': '1', 'location_id': '2'} +# ) +# ] +# STEP_END + +# STEP_START xread +res17 = r.xread(streams={"race:france": 0}, count=2) +print( + res17 +) +# >>> [ +# ['race:france', [ +# ('1692629576966-0', +# {'rider': 'Castilla', 'speed': '30.2', 'position': '1', 'location_id': '1'} +# ), +# ('1692629594113-0', +# {'rider': 'Norem', 'speed': '28.8', 'position': '3', 'location_id': '1'} +# ) +# ] +# ] +# ] +# STEP_END + +# STEP_START xgroup_create +res18 = r.xgroup_create("race:france", "france_riders", "$") +print(res18) # >>> True +# STEP_END + +# STEP_START xgroup_create_mkstream +res19 = r.xgroup_create("race:italy", "italy_riders", "$", mkstream=True) +print(res19) # >>> True +# STEP_END + +# STEP_START xgroup_read +r.xadd("race:italy", {"rider": "Castilla"}) +r.xadd("race:italy", {"rider": "Royce"}) +r.xadd("race:italy", {"rider": "Sam-Bodden"}) +r.xadd("race:italy", {"rider": "Prickett"}) +r.xadd("race:italy", {"rider": "Norem"}) + +res20 = r.xreadgroup( + streams={"race:italy": ">"}, + consumername="Alice", + groupname="italy_riders", + count=1, +) +print(res20) # >>> [['race:italy', [('1692629925771-0', {'rider': 'Castilla'})]]] +# STEP_END + +# STEP_START xgroup_read_id +res21 = r.xreadgroup( + streams={"race:italy": 0}, + consumername="Alice", + groupname="italy_riders", + count=1, +) +print(res21) # >>> [['race:italy', [('1692629925771-0', {'rider': 'Castilla'})]]] +# STEP_END + +# STEP_START xack +res22 = r.xack("race:italy", "italy_riders", "1692629925771-0") +print(res22) # >>> 1 + +res23 = r.xreadgroup( + streams={"race:italy": 0}, + consumername="Alice", + groupname="italy_riders", + count=1, +) +print(res23) # >>> [['race:italy', []]] +# STEP_END + +# STEP_START xgroup_read_bob +res24 = r.xreadgroup( + streams={"race:italy": ">"}, + consumername="Bob", + groupname="italy_riders", + count=2, +) +print( + res24 +) +# >>> [ +# ['race:italy', [ +# ('1692629925789-0', +# {'rider': 'Royce'} +# ), +# ('1692629925790-0', +# {'rider': 'Sam-Bodden'} +# ) +# ] +# ] +# ] +# STEP_END + +# STEP_START xpending +res25 = r.xpending("race:italy", "italy_riders") +print( + res25 +) +# >>> { +# 'pending': 2, 'min': '1692629925789-0', 'max': '1692629925790-0', +# 'consumers': [{'name': 'Bob', 'pending': 2}] +# } +# STEP_END + +# STEP_START xpending_plus_minus +res26 = r.xpending_range("race:italy", "italy_riders", "-", "+", 10) +print( + res26 +) +# >>> [ +# { +# 'message_id': '1692629925789-0', 'consumer': 'Bob', +# 'time_since_delivered': 31084, 'times_delivered': 1 +# }, +# { +# 'message_id': '1692629925790-0', 'consumer': 'Bob', +# 'time_since_delivered': 31084, 'times_delivered': 1 +# } +# ] +# STEP_END + +# STEP_START xrange_pending +res27 = r.xrange("race:italy", "1692629925789-0", "1692629925789-0") +print(res27) # >>> [('1692629925789-0', {'rider': 'Royce'})] +# STEP_END + +# STEP_START xclaim +res28 = r.xclaim("race:italy", "italy_riders", "Alice", 60000, ["1692629925789-0"]) +print(res28) # >>> [('1692629925789-0', {'rider': 'Royce'})] +# STEP_END + +# STEP_START xautoclaim +res29 = r.xautoclaim("race:italy", "italy_riders", "Alice", 1, "0-0", 1) +print(res29) # >>> ['1692629925790-0', [('1692629925789-0', {'rider': 'Royce'})]] +# STEP_END + +# STEP_START xautoclaim_cursor +res30 = r.xautoclaim("race:italy", "italy_riders", "Alice", 1, "(1692629925789-0", 1) +print(res30) # >>> ['0-0', [('1692629925790-0', {'rider': 'Sam-Bodden'})]] +# STEP_END + +# STEP_START xinfo +res31 = r.xinfo_stream("race:italy") +print( + res31 +) +# >>> { +# 'length': 5, 'radix-tree-keys': 1, 'radix-tree-nodes': 2, +# 'last-generated-id': '1692629926436-0', 'groups': 1, +# 'first-entry': ('1692629925771-0', {'rider': 'Castilla'}), +# 'last-entry': ('1692629926436-0', {'rider': 'Norem'}) +# } +# STEP_END + +# STEP_START xinfo_groups +res32 = r.xinfo_groups("race:italy") +print( + res32 +) +# >>> [ +# { +# 'name': 'italy_riders', 'consumers': 2, 'pending': 2, +# 'last-delivered-id': '1692629925790-0' +# } +# ] +# STEP_END + +# STEP_START xinfo_consumers +res33 = r.xinfo_consumers("race:italy", "italy_riders") +print( + res33 +) +# >>> [ +# {'name': 'Alice', 'pending': 2, 'idle': 199332}, +# {'name': 'Bob', 'pending': 0, 'idle': 489170} +# ] +# STEP_END + +# STEP_START maxlen +r.xadd("race:italy", {"rider": "Jones"}, maxlen=2) +r.xadd("race:italy", {"rider": "Wood"}, maxlen=2) +r.xadd("race:italy", {"rider": "Henshaw"}, maxlen=2) + +res34 = r.xlen("race:italy") +print(res34) # >>> 8 + +res35 = r.xrange("race:italy", "-", "+") +print( + res35 +) +# >>> [ +# ('1692629925771-0', {'rider': 'Castilla'}), +# ('1692629925789-0', {'rider': 'Royce'}), +# ('1692629925790-0', {'rider': 'Sam-Bodden'}), +# ('1692629925791-0', {'rider': 'Prickett'}), +# ('1692629926436-0', {'rider': 'Norem'}), +# ('1692630612602-0', {'rider': 'Jones'}), +# ('1692630641947-0', {'rider': 'Wood'}), +# ('1692630648281-0', {'rider': 'Henshaw'}) +# ] + +r.xadd("race:italy", {"rider": "Smith"}, maxlen=2, approximate=False) + +res36 = r.xrange("race:italy", "-", "+") +print( + res36 +) +# >>> [ +# ('1692630648281-0', {'rider': 'Henshaw'}), +# ('1692631018238-0', {'rider': 'Smith'}) +# ] +# STEP_END + +# STEP_START xtrim +res37 = r.xtrim("race:italy", maxlen=10, approximate=False) +print(res37) # >>> 0 +# STEP_END + +# STEP_START xtrim2 +res38 = r.xtrim("race:italy", maxlen=10) +print(res38) # >>> 0 +# STEP_END + +# STEP_START xdel +res39 = r.xrange("race:italy", "-", "+") +print( + res39 +) +# >>> [ +# ('1692630648281-0', {'rider': 'Henshaw'}), +# ('1692631018238-0', {'rider': 'Smith'}) +# ] + +res40 = r.xdel("race:italy", "1692631018238-0") +print(res40) # >>> 1 + +res41 = r.xrange("race:italy", "-", "+") +print(res41) # >>> [('1692630648281-0', {'rider': 'Henshaw'})] +# STEP_END diff --git a/doctests/dt_string.py b/doctests/dt_string.py new file mode 100644 index 0000000000..7d4cc41b89 --- /dev/null +++ b/doctests/dt_string.py @@ -0,0 +1,61 @@ +# EXAMPLE: set_tutorial +# HIDE_START +""" +Code samples for String doc pages: + https://redis.io/docs/latest/develop/data-types/strings/ +""" + +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# STEP_START set_get +res1 = r.set("bike:1", "Deimos") +print(res1) # True +res2 = r.get("bike:1") +print(res2) # Deimos +# STEP_END + +# REMOVE_START +assert res1 +assert res2 == "Deimos" +# REMOVE_END + +# STEP_START setnx_xx +res3 = r.set("bike:1", "bike", nx=True) +print(res3) # None +print(r.get("bike:1")) # Deimos +res4 = r.set("bike:1", "bike", xx=True) +print(res4) # True +# STEP_END + +# REMOVE_START +assert res3 is None +assert res4 +# REMOVE_END + +# STEP_START mset +res5 = r.mset({"bike:1": "Deimos", "bike:2": "Ares", "bike:3": "Vanth"}) +print(res5) # True +res6 = r.mget(["bike:1", "bike:2", "bike:3"]) +print(res6) # ['Deimos', 'Ares', 'Vanth'] +# STEP_END + +# REMOVE_START +assert res5 +assert res6 == ["Deimos", "Ares", "Vanth"] +# REMOVE_END + +# STEP_START incr +r.set("total_crashes", 0) +res7 = r.incr("total_crashes") +print(res7) # 1 +res8 = r.incrby("total_crashes", 10) +print(res8) # 11 +# STEP_END + +# REMOVE_START +assert res7 == 1 +assert res8 == 11 +# REMOVE_END diff --git a/doctests/dt_tdigest.py b/doctests/dt_tdigest.py new file mode 100644 index 0000000000..61124ac79b --- /dev/null +++ b/doctests/dt_tdigest.py @@ -0,0 +1,81 @@ +# EXAMPLE: tdigest_tutorial +# HIDE_START +""" +Code samples for t-digest pages: + https://redis.io/docs/latest/develop/data-types/probabilistic/t-digest/ +""" + +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# REMOVE_START +r.delete("racer_ages") +r.delete("bikes:sales") +# REMOVE_END + +# STEP_START tdig_start +res1 = r.tdigest().create("bikes:sales", 100) +print(res1) # >>> True + +res2 = r.tdigest().add("bikes:sales", [21]) +print(res2) # >>> OK + +res3 = r.tdigest().add("bikes:sales", [150, 95, 75, 34]) +print(res3) # >>> OK +# STEP_END + +# REMOVE_START +assert res1 is True +# REMOVE_END + +# STEP_START tdig_cdf +res4 = r.tdigest().create("racer_ages") +print(res4) # >>> True + +res5 = r.tdigest().add( + "racer_ages", + [ + 45.88, + 44.2, + 58.03, + 19.76, + 39.84, + 69.28, + 50.97, + 25.41, + 19.27, + 85.71, + 42.63, + ], +) +print(res5) # >>> OK + +res6 = r.tdigest().rank("racer_ages", 50) +print(res6) # >>> [7] + +res7 = r.tdigest().rank("racer_ages", 50, 40) +print(res7) # >>> [7, 4] +# STEP_END + +# STEP_START tdig_quant +res8 = r.tdigest().quantile("racer_ages", 0.5) +print(res8) # >>> [44.2] + +res9 = r.tdigest().byrank("racer_ages", 4) +print(res9) # >>> [42.63] +# STEP_END + +# STEP_START tdig_min +res10 = r.tdigest().min("racer_ages") +print(res10) # >>> 19.27 + +res11 = r.tdigest().max("racer_ages") +print(res11) # >>> 85.71 +# STEP_END + +# STEP_START tdig_reset +res12 = r.tdigest().reset("racer_ages") +print(res12) # >>> OK +# STEP_END diff --git a/doctests/dt_topk.py b/doctests/dt_topk.py new file mode 100644 index 0000000000..9767e3f265 --- /dev/null +++ b/doctests/dt_topk.py @@ -0,0 +1,38 @@ +# EXAMPLE: topk_tutorial +# HIDE_START +""" +Code samples for Top-K pages: + https://redis.io/docs/latest/develop/data-types/probabilistic/top-k/ +""" + +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# REMOVE_START +r.delete("bikes:keywords") +# REMOVE_END + +# STEP_START topk +res1 = r.topk().reserve("bikes:keywords", 5, 2000, 7, 0.925) +print(res1) # >>> True + +res2 = r.topk().add( + "bikes:keywords", + "store", + "seat", + "handlebars", + "handles", + "pedals", + "tires", + "store", + "seat", +) +print(res2) # >>> [None, None, None, None, None, 'handlebars', None, None] + +res3 = r.topk().list("bikes:keywords") +print(res3) # >>> ['store', 'seat', 'pedals', 'tires', 'handles'] + +res4 = r.topk().query("bikes:keywords", "store", "handlebars") +print(res4) # >>> [1, 0] diff --git a/doctests/requirements.txt b/doctests/requirements.txt new file mode 100644 index 0000000000..209d87b9c8 --- /dev/null +++ b/doctests/requirements.txt @@ -0,0 +1,5 @@ +numpy +pandas +requests +sentence_transformers +tabulate diff --git a/doctests/run_examples.sh b/doctests/run_examples.sh new file mode 100644 index 0000000000..51360639ee --- /dev/null +++ b/doctests/run_examples.sh @@ -0,0 +1,15 @@ +#!/bin/sh + + +basepath=`readlink -f $1` +if [ $? -ne 0 ]; then +basepath=`readlink -f $(dirname $0)` +fi +echo "No path specified, using ${basepath}" + +set -e +cd ${basepath} +for i in `ls ${basepath}/*.py`; do + redis-cli flushdb + python $i +done diff --git a/doctests/search_quickstart.py b/doctests/search_quickstart.py new file mode 100644 index 0000000000..e190393b16 --- /dev/null +++ b/doctests/search_quickstart.py @@ -0,0 +1,425 @@ +# 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 diff --git a/doctests/search_vss.py b/doctests/search_vss.py new file mode 100644 index 0000000000..8b4884727a --- /dev/null +++ b/doctests/search_vss.py @@ -0,0 +1,347 @@ +# EXAMPLE: search_vss +# HIDE_START +""" +Code samples for vector database quickstart pages: + https://redis.io/docs/latest/develop/get-started/vector-database/ +""" +# HIDE_END + +# STEP_START imports +import json +import time + +import numpy as np +import pandas as pd +import requests +import redis +from redis.commands.search.field import ( + NumericField, + TagField, + TextField, + VectorField, +) +from redis.commands.search.indexDefinition import IndexDefinition, IndexType +from redis.commands.search.query import Query +from sentence_transformers import SentenceTransformer + +# STEP_END + +# STEP_START get_data +URL = ("https://raw.githubusercontent.com/bsbodden/redis_vss_getting_started" + "/main/data/bikes.json" + ) +response = requests.get(URL, timeout=10) +bikes = response.json() +# STEP_END +# REMOVE_START +assert bikes[0]["model"] == "Jigger" +# REMOVE_END + +# STEP_START dump_data +json.dumps(bikes[0], indent=2) +# STEP_END + +# STEP_START connect +client = redis.Redis(host="localhost", port=6379, decode_responses=True) +# STEP_END + +# STEP_START connection_test +res = client.ping() +# >>> True +# STEP_END +# REMOVE_START +assert res +# REMOVE_END + +# STEP_START load_data +pipeline = client.pipeline() +for i, bike in enumerate(bikes, start=1): + redis_key = f"bikes:{i:03}" + pipeline.json().set(redis_key, "$", bike) +res = pipeline.execute() +# >>> [True, True, True, True, True, True, True, True, True, True, True] +# STEP_END +# REMOVE_START +assert res == [True, True, True, True, True, True, True, True, True, True, True] +# REMOVE_END + +# STEP_START get +res = client.json().get("bikes:010", "$.model") +# >>> ['Summit'] +# STEP_END +# REMOVE_START +assert res == ["Summit"] +# REMOVE_END + +# STEP_START get_keys +keys = sorted(client.keys("bikes:*")) +# >>> ['bikes:001', 'bikes:002', ..., 'bikes:011'] +# STEP_END +# REMOVE_START +assert keys[0] == "bikes:001" +# REMOVE_END + +# STEP_START generate_embeddings +descriptions = client.json().mget(keys, "$.description") +descriptions = [item for sublist in descriptions for item in sublist] +embedder = SentenceTransformer("msmarco-distilbert-base-v4") +embeddings = embedder.encode(descriptions).astype(np.float32).tolist() +VECTOR_DIMENSION = len(embeddings[0]) +# >>> 768 +# STEP_END +# REMOVE_START +assert VECTOR_DIMENSION == 768 +# REMOVE_END + +# STEP_START load_embeddings +pipeline = client.pipeline() +for key, embedding in zip(keys, embeddings): + pipeline.json().set(key, "$.description_embeddings", embedding) +pipeline.execute() +# >>> [True, True, True, True, True, True, True, True, True, True, True] +# STEP_END + +# STEP_START dump_example +res = client.json().get("bikes:010") +# >>> +# { +# "model": "Summit", +# "brand": "nHill", +# "price": 1200, +# "type": "Mountain Bike", +# "specs": { +# "material": "alloy", +# "weight": "11.3" +# }, +# "description": "This budget mountain bike from nHill performs well..." +# "description_embeddings": [ +# -0.538114607334137, +# -0.49465855956077576, +# -0.025176964700222015, +# ... +# ] +# } +# STEP_END +# REMOVE_START +assert len(res["description_embeddings"]) == 768 +# REMOVE_END + +# STEP_START create_index +schema = ( + TextField("$.model", no_stem=True, as_name="model"), + TextField("$.brand", no_stem=True, as_name="brand"), + NumericField("$.price", as_name="price"), + TagField("$.type", as_name="type"), + TextField("$.description", as_name="description"), + VectorField( + "$.description_embeddings", + "FLAT", + { + "TYPE": "FLOAT32", + "DIM": VECTOR_DIMENSION, + "DISTANCE_METRIC": "COSINE", + }, + as_name="vector", + ), +) +definition = IndexDefinition(prefix=["bikes:"], index_type=IndexType.JSON) +res = client.ft("idx:bikes_vss").create_index(fields=schema, definition=definition) +# >>> 'OK' +# STEP_END +# REMOVE_START +assert res == "OK" +time.sleep(2) +# REMOVE_END + +# STEP_START validate_index +info = client.ft("idx:bikes_vss").info() +num_docs = info["num_docs"] +indexing_failures = info["hash_indexing_failures"] +# print(f"{num_docs} documents indexed with {indexing_failures} failures") +# >>> 11 documents indexed with 0 failures +# STEP_END +# REMOVE_START +assert (num_docs == "11") and (indexing_failures == "0") +# REMOVE_END + +# STEP_START simple_query_1 +query = Query("@brand:Peaknetic") +res = client.ft("idx:bikes_vss").search(query).docs +# print(res) +# >>> [ +# Document { +# 'id': 'bikes:008', +# 'payload': None, +# 'brand': 'Peaknetic', +# 'model': 'Soothe Electric bike', +# 'price': '1950', 'description_embeddings': ... +# STEP_END +# REMOVE_START + +assert all( + item in [x.__dict__["id"] for x in res] for item in ["bikes:008", "bikes:009"] +) +# REMOVE_END + +# STEP_START simple_query_2 +query = Query("@brand:Peaknetic").return_fields("id", "brand", "model", "price") +res = client.ft("idx:bikes_vss").search(query).docs +# print(res) +# >>> [ +# Document { +# 'id': 'bikes:008', +# 'payload': None, +# 'brand': 'Peaknetic', +# 'model': 'Soothe Electric bike', +# 'price': '1950' +# }, +# Document { +# 'id': 'bikes:009', +# 'payload': None, +# 'brand': 'Peaknetic', +# 'model': 'Secto', +# 'price': '430' +# } +# ] +# STEP_END +# REMOVE_START +assert all( + item in [x.__dict__["id"] for x in res] for item in ["bikes:008", "bikes:009"] +) +# REMOVE_END + +# STEP_START simple_query_3 +query = Query("@brand:Peaknetic @price:[0 1000]").return_fields( + "id", "brand", "model", "price" +) +res = client.ft("idx:bikes_vss").search(query).docs +# print(res) +# >>> [ +# Document { +# 'id': 'bikes:009', +# 'payload': None, +# 'brand': 'Peaknetic', +# 'model': 'Secto', +# 'price': '430' +# } +# ] +# STEP_END +# REMOVE_START +assert all(item in [x.__dict__["id"] for x in res] for item in ["bikes:009"]) +# REMOVE_END + +# STEP_START def_bulk_queries +queries = [ + "Bike for small kids", + "Best Mountain bikes for kids", + "Cheap Mountain bike for kids", + "Female specific mountain bike", + "Road bike for beginners", + "Commuter bike for people over 60", + "Comfortable commuter bike", + "Good bike for college students", + "Mountain bike for beginners", + "Vintage bike", + "Comfortable city bike", +] +# STEP_END + +# STEP_START enc_bulk_queries +encoded_queries = embedder.encode(queries) +len(encoded_queries) +# >>> 11 +# STEP_END +# REMOVE_START +assert len(encoded_queries) == 11 +# REMOVE_END + + +# STEP_START define_bulk_query +def create_query_table(query, queries, encoded_queries, extra_params=None): + """ + Creates a query table. + """ + results_list = [] + for i, encoded_query in enumerate(encoded_queries): + result_docs = ( + client.ft("idx:bikes_vss") + .search( + query, + {"query_vector": np.array(encoded_query, dtype=np.float32).tobytes()} + | (extra_params if extra_params else {}), + ) + .docs + ) + for doc in result_docs: + vector_score = round(1 - float(doc.vector_score), 2) + results_list.append( + { + "query": queries[i], + "score": vector_score, + "id": doc.id, + "brand": doc.brand, + "model": doc.model, + "description": doc.description, + } + ) + + # Optional: convert the table to Markdown using Pandas + queries_table = pd.DataFrame(results_list) + queries_table.sort_values( + by=["query", "score"], ascending=[True, False], inplace=True + ) + queries_table["query"] = queries_table.groupby("query")["query"].transform( + lambda x: [x.iloc[0]] + [""] * (len(x) - 1) + ) + queries_table["description"] = queries_table["description"].apply( + lambda x: (x[:497] + "...") if len(x) > 500 else x + ) + return queries_table.to_markdown(index=False) + + +# STEP_END + +# STEP_START run_knn_query +query = ( + Query("(*)=>[KNN 3 @vector $query_vector AS vector_score]") + .sort_by("vector_score") + .return_fields("vector_score", "id", "brand", "model", "description") + .dialect(2) +) + +table = create_query_table(query, queries, encoded_queries) +print(table) +# >>> | Best Mountain bikes for kids | 0.54 | bikes:003... +# STEP_END + +# STEP_START run_hybrid_query +hybrid_query = ( + Query("(@brand:Peaknetic)=>[KNN 3 @vector $query_vector AS vector_score]") + .sort_by("vector_score") + .return_fields("vector_score", "id", "brand", "model", "description") + .dialect(2) +) +table = create_query_table(hybrid_query, queries, encoded_queries) +print(table) +# >>> | Best Mountain bikes for kids | 0.3 | bikes:008... +# STEP_END + +# STEP_START run_range_query +range_query = ( + Query( + "@vector:[VECTOR_RANGE $range $query_vector]=>" + "{$YIELD_DISTANCE_AS: vector_score}" + ) + .sort_by("vector_score") + .return_fields("vector_score", "id", "brand", "model", "description") + .paging(0, 4) + .dialect(2) +) +table = create_query_table( + range_query, queries[:1], + encoded_queries[:1], + {"range": 0.55} +) +print(table) +# >>> | Bike for small kids | 0.52 | bikes:001 | Velorim |... +# STEP_END diff --git a/doctests/string_set_get.py b/doctests/string_set_get.py new file mode 100644 index 0000000000..d5f260c281 --- /dev/null +++ b/doctests/string_set_get.py @@ -0,0 +1,25 @@ +# EXAMPLE: set_and_get +# HIDE_START +""" +Code samples for data structure store quickstart pages: + https://redis.io/docs/latest/develop/get-started/data-store/ +""" + +import redis + +r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True) +# HIDE_END + +res = r.set("bike:1", "Process 134") +print(res) +# >>> True +# REMOVE_START +assert res +# REMOVE_END + +res = r.get("bike:1") +print(res) +# >>> "Process 134" +# REMOVE_START +assert res == "Process 134" +# REMOVE_END