├── .gitignore ├── AUTHORS ├── README.rst ├── commands.json ├── debian ├── changelog ├── compat ├── control ├── copyright ├── rules └── source │ └── format ├── examples ├── auth.py ├── genauth.py ├── reconnect.py └── simple.py ├── gen_commands.py ├── setup.py ├── tests ├── __init__.py ├── test_client.py ├── test_handler.py └── test_pipeline.py └── toredis ├── __init__.py ├── _compat.py ├── client.py ├── commands.py └── pipeline.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.pyc 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Josh Marshall 2 | Andrew Grigorev 3 | Serge S. Koval 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | TOREDIS 2 | ======= 3 | 4 | This is minimalistic, but feature rich redis client for Tornado built on top of `hiredis `_ protocol parser. 5 | 6 | Supports all redis commands, which are auto-generated from the redis `JSON documentation file `_. 7 | 8 | Key design points: 9 | 10 | 1. While toredis attempts to add some syntactical sugar to the API, all responses are returned "as is". For example, if command returns 11 | list of items and developer requested only one key, list with one entry will be returned. For example:: 12 | 13 | def handle(self, result): 14 | print(len(result)) 15 | 16 | conn.hkeys('test1', handle) 17 | 18 | 19 | 2. Most redis commands accept one or more keys. Toredis adds a bit of logic to handle single key or array of keys. Due to python 20 | limitations, it is not possible to use ``*args`` with named ``callback`` argument, so you will have to pass array of key names:: 21 | 22 | # This will work 23 | conn.blpop('test', callback=callback) 24 | conn.blpop(['test', 'test2'], callback=callback) 25 | 26 | # This won't work 27 | conn.blpop('test', 'test2', callback=callback) 28 | 29 | 30 | 3. If redis connection will be dropped while waiting for response, callback will be triggered with `None` as a value. 31 | 32 | 4. Toredis does not provide reconnection feature, but you can override :meth:`~toredis.Client.on_disconnect` method and implement your reconnection logic. 33 | 34 | You can find command `documentation here `_ (will be moved to rtd later). 35 | 36 | Pipelining is also supported:: 37 | 38 | # For more than one pipeline for connection create it with Pipeline(conn) 39 | pipeline = conn.pipeline() 40 | pipeline.set('foo', 'bar') 41 | pipeline.get('foo') 42 | pipeline.send(callback=callback) 43 | 44 | For more examples please refer to tests. 45 | More on `redis pipelining `_. 46 | 47 | Things missing: 48 | 49 | * Backport pure-python redis protocol parser (for PyPy support) 50 | * Connection pools 51 | -------------------------------------------------------------------------------- /commands.json: -------------------------------------------------------------------------------- 1 | { 2 | "APPEND": { 3 | "summary": "Append a value to a key", 4 | "complexity": "O(1). The amortized time complexity is O(1) assuming the appended value is small and the already present value is of any size, since the dynamic string library used by Redis will double the free space available on every reallocation.", 5 | "arguments": [ 6 | { 7 | "name": "key", 8 | "type": "key" 9 | }, 10 | { 11 | "name": "value", 12 | "type": "string" 13 | } 14 | ], 15 | "since": "2.0.0", 16 | "group": "string" 17 | }, 18 | "AUTH": { 19 | "summary": "Authenticate to the server", 20 | "arguments": [ 21 | { 22 | "name": "password", 23 | "type": "string" 24 | } 25 | ], 26 | "since": "1.0.0", 27 | "group": "connection" 28 | }, 29 | "BGREWRITEAOF": { 30 | "summary": "Asynchronously rewrite the append-only file", 31 | "since": "1.0.0", 32 | "group": "server" 33 | }, 34 | "BGSAVE": { 35 | "summary": "Asynchronously save the dataset to disk", 36 | "since": "1.0.0", 37 | "group": "server" 38 | }, 39 | "BITCOUNT": { 40 | "summary": "Count set bits in a string", 41 | "complexity": "O(N)", 42 | "arguments": [ 43 | { 44 | "name": "key", 45 | "type": "key" 46 | }, 47 | { 48 | "name": "start", 49 | "type": "integer", 50 | "optional": true 51 | }, 52 | { 53 | "name": "end", 54 | "type": "integer", 55 | "optional": true 56 | } 57 | ], 58 | "since": "2.6.0", 59 | "group": "string" 60 | }, 61 | "BITOP": { 62 | "summary": "Perform bitwise operations between strings", 63 | "complexity": "O(N)", 64 | "arguments": [ 65 | { 66 | "name": "operation", 67 | "type": "string" 68 | }, 69 | { 70 | "name": "destkey", 71 | "type": "key" 72 | }, 73 | { 74 | "name": "key", 75 | "type": "key", 76 | "multiple": true 77 | } 78 | ], 79 | "since": "2.6.0", 80 | "group": "string" 81 | }, 82 | "BLPOP": { 83 | "summary": "Remove and get the first element in a list, or block until one is available", 84 | "complexity": "O(1)", 85 | "arguments": [ 86 | { 87 | "name": "key", 88 | "type": "key", 89 | "multiple": true 90 | }, 91 | { 92 | "name": "timeout", 93 | "type": "integer" 94 | } 95 | ], 96 | "since": "2.0.0", 97 | "group": "list" 98 | }, 99 | "BRPOP": { 100 | "summary": "Remove and get the last element in a list, or block until one is available", 101 | "complexity": "O(1)", 102 | "arguments": [ 103 | { 104 | "name": "key", 105 | "type": "key", 106 | "multiple": true 107 | }, 108 | { 109 | "name": "timeout", 110 | "type": "integer" 111 | } 112 | ], 113 | "since": "2.0.0", 114 | "group": "list" 115 | }, 116 | "BRPOPLPUSH": { 117 | "summary": "Pop a value from a list, push it to another list and return it; or block until one is available", 118 | "complexity": "O(1)", 119 | "arguments": [ 120 | { 121 | "name": "source", 122 | "type": "key" 123 | }, 124 | { 125 | "name": "destination", 126 | "type": "key" 127 | }, 128 | { 129 | "name": "timeout", 130 | "type": "integer" 131 | } 132 | ], 133 | "since": "2.2.0", 134 | "group": "list" 135 | }, 136 | "CLIENT KILL": { 137 | "summary": "Kill the connection of a client", 138 | "complexity": "O(N) where N is the number of client connections", 139 | "arguments": [ 140 | { 141 | "name": "ip:port", 142 | "type": "string" 143 | } 144 | ], 145 | "since": "2.4.0", 146 | "group": "server" 147 | }, 148 | "CLIENT LIST": { 149 | "summary": "Get the list of client connections", 150 | "complexity": "O(N) where N is the number of client connections", 151 | "since": "2.4.0", 152 | "group": "server" 153 | }, 154 | "CONFIG GET": { 155 | "summary": "Get the value of a configuration parameter", 156 | "arguments": [ 157 | { 158 | "name": "parameter", 159 | "type": "string" 160 | } 161 | ], 162 | "since": "2.0.0", 163 | "group": "server" 164 | }, 165 | "CONFIG SET": { 166 | "summary": "Set a configuration parameter to the given value", 167 | "arguments": [ 168 | { 169 | "name": "parameter", 170 | "type": "string" 171 | }, 172 | { 173 | "name": "value", 174 | "type": "string" 175 | } 176 | ], 177 | "since": "2.0.0", 178 | "group": "server" 179 | }, 180 | "CONFIG RESETSTAT": { 181 | "summary": "Reset the stats returned by INFO", 182 | "complexity": "O(1)", 183 | "since": "2.0.0", 184 | "group": "server" 185 | }, 186 | "DBSIZE": { 187 | "summary": "Return the number of keys in the selected database", 188 | "since": "1.0.0", 189 | "group": "server" 190 | }, 191 | "DEBUG OBJECT": { 192 | "summary": "Get debugging information about a key", 193 | "arguments": [ 194 | { 195 | "name": "key", 196 | "type": "key" 197 | } 198 | ], 199 | "since": "1.0.0", 200 | "group": "server" 201 | }, 202 | "DEBUG SEGFAULT": { 203 | "summary": "Make the server crash", 204 | "since": "1.0.0", 205 | "group": "server" 206 | }, 207 | "DECR": { 208 | "summary": "Decrement the integer value of a key by one", 209 | "complexity": "O(1)", 210 | "arguments": [ 211 | { 212 | "name": "key", 213 | "type": "key" 214 | } 215 | ], 216 | "since": "1.0.0", 217 | "group": "string" 218 | }, 219 | "DECRBY": { 220 | "summary": "Decrement the integer value of a key by the given number", 221 | "complexity": "O(1)", 222 | "arguments": [ 223 | { 224 | "name": "key", 225 | "type": "key" 226 | }, 227 | { 228 | "name": "decrement", 229 | "type": "integer" 230 | } 231 | ], 232 | "since": "1.0.0", 233 | "group": "string" 234 | }, 235 | "DEL": { 236 | "summary": "Delete a key", 237 | "complexity": "O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).", 238 | "arguments": [ 239 | { 240 | "name": "key", 241 | "type": "key", 242 | "multiple": true 243 | } 244 | ], 245 | "since": "1.0.0", 246 | "group": "generic" 247 | }, 248 | "DISCARD": { 249 | "summary": "Discard all commands issued after MULTI", 250 | "since": "2.0.0", 251 | "group": "transactions" 252 | }, 253 | "DUMP": { 254 | "summary": "Return a serialized version of the value stored at the specified key.", 255 | "complexity": "O(1) to access the key and additional O(N*M) to serialized it, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1).", 256 | "arguments": [ 257 | { 258 | "name": "key", 259 | "type": "key" 260 | } 261 | ], 262 | "since": "2.6.0", 263 | "group": "generic" 264 | }, 265 | "ECHO": { 266 | "summary": "Echo the given string", 267 | "arguments": [ 268 | { 269 | "name": "message", 270 | "type": "string" 271 | } 272 | ], 273 | "since": "1.0.0", 274 | "group": "connection" 275 | }, 276 | "EVAL": { 277 | "summary": "Execute a Lua script server side", 278 | "complexity": "Depends on the script that is executed.", 279 | "arguments": [ 280 | { 281 | "name": "script", 282 | "type": "string" 283 | }, 284 | { 285 | "name": "numkeys", 286 | "type": "integer" 287 | }, 288 | { 289 | "name": "key", 290 | "type": "key", 291 | "multiple": true 292 | }, 293 | { 294 | "name": "arg", 295 | "type": "string", 296 | "multiple": true 297 | } 298 | ], 299 | "since": "2.6.0", 300 | "group": "scripting" 301 | }, 302 | "EVALSHA": { 303 | "summary": "Execute a Lua script server side", 304 | "complexity": "Depends on the script that is executed.", 305 | "arguments": [ 306 | { 307 | "name": "sha1", 308 | "type": "string" 309 | }, 310 | { 311 | "name": "numkeys", 312 | "type": "integer" 313 | }, 314 | { 315 | "name": "key", 316 | "type": "key", 317 | "multiple": true 318 | }, 319 | { 320 | "name": "arg", 321 | "type": "string", 322 | "multiple": true 323 | } 324 | ], 325 | "since": "2.6.0", 326 | "group": "scripting" 327 | }, 328 | "EXEC": { 329 | "summary": "Execute all commands issued after MULTI", 330 | "since": "1.2.0", 331 | "group": "transactions" 332 | }, 333 | "EXISTS": { 334 | "summary": "Determine if a key exists", 335 | "complexity": "O(1)", 336 | "arguments": [ 337 | { 338 | "name": "key", 339 | "type": "key" 340 | } 341 | ], 342 | "since": "1.0.0", 343 | "group": "generic" 344 | }, 345 | "EXPIRE": { 346 | "summary": "Set a key's time to live in seconds", 347 | "complexity": "O(1)", 348 | "arguments": [ 349 | { 350 | "name": "key", 351 | "type": "key" 352 | }, 353 | { 354 | "name": "seconds", 355 | "type": "integer" 356 | } 357 | ], 358 | "since": "1.0.0", 359 | "group": "generic" 360 | }, 361 | "EXPIREAT": { 362 | "summary": "Set the expiration for a key as a UNIX timestamp", 363 | "complexity": "O(1)", 364 | "arguments": [ 365 | { 366 | "name": "key", 367 | "type": "key" 368 | }, 369 | { 370 | "name": "timestamp", 371 | "type": "posix time" 372 | } 373 | ], 374 | "since": "1.2.0", 375 | "group": "generic" 376 | }, 377 | "FLUSHALL": { 378 | "summary": "Remove all keys from all databases", 379 | "since": "1.0.0", 380 | "group": "server" 381 | }, 382 | "FLUSHDB": { 383 | "summary": "Remove all keys from the current database", 384 | "since": "1.0.0", 385 | "group": "server" 386 | }, 387 | "GET": { 388 | "summary": "Get the value of a key", 389 | "complexity": "O(1)", 390 | "arguments": [ 391 | { 392 | "name": "key", 393 | "type": "key" 394 | } 395 | ], 396 | "since": "1.0.0", 397 | "group": "string" 398 | }, 399 | "GETBIT": { 400 | "summary": "Returns the bit value at offset in the string value stored at key", 401 | "complexity": "O(1)", 402 | "arguments": [ 403 | { 404 | "name": "key", 405 | "type": "key" 406 | }, 407 | { 408 | "name": "offset", 409 | "type": "integer" 410 | } 411 | ], 412 | "since": "2.2.0", 413 | "group": "string" 414 | }, 415 | "GETRANGE": { 416 | "summary": "Get a substring of the string stored at a key", 417 | "complexity": "O(N) where N is the length of the returned string. The complexity is ultimately determined by the returned length, but because creating a substring from an existing string is very cheap, it can be considered O(1) for small strings.", 418 | "arguments": [ 419 | { 420 | "name": "key", 421 | "type": "key" 422 | }, 423 | { 424 | "name": "start", 425 | "type": "integer" 426 | }, 427 | { 428 | "name": "end", 429 | "type": "integer" 430 | } 431 | ], 432 | "since": "2.4.0", 433 | "group": "string" 434 | }, 435 | "GETSET": { 436 | "summary": "Set the string value of a key and return its old value", 437 | "complexity": "O(1)", 438 | "arguments": [ 439 | { 440 | "name": "key", 441 | "type": "key" 442 | }, 443 | { 444 | "name": "value", 445 | "type": "string" 446 | } 447 | ], 448 | "since": "1.0.0", 449 | "group": "string" 450 | }, 451 | "HDEL": { 452 | "summary": "Delete one or more hash fields", 453 | "complexity": "O(N) where N is the number of fields to be removed.", 454 | "arguments": [ 455 | { 456 | "name": "key", 457 | "type": "key" 458 | }, 459 | { 460 | "name": "field", 461 | "type": "string", 462 | "multiple": true 463 | } 464 | ], 465 | "since": "2.0.0", 466 | "group": "hash" 467 | }, 468 | "HEXISTS": { 469 | "summary": "Determine if a hash field exists", 470 | "complexity": "O(1)", 471 | "arguments": [ 472 | { 473 | "name": "key", 474 | "type": "key" 475 | }, 476 | { 477 | "name": "field", 478 | "type": "string" 479 | } 480 | ], 481 | "since": "2.0.0", 482 | "group": "hash" 483 | }, 484 | "HGET": { 485 | "summary": "Get the value of a hash field", 486 | "complexity": "O(1)", 487 | "arguments": [ 488 | { 489 | "name": "key", 490 | "type": "key" 491 | }, 492 | { 493 | "name": "field", 494 | "type": "string" 495 | } 496 | ], 497 | "since": "2.0.0", 498 | "group": "hash" 499 | }, 500 | "HGETALL": { 501 | "summary": "Get all the fields and values in a hash", 502 | "complexity": "O(N) where N is the size of the hash.", 503 | "arguments": [ 504 | { 505 | "name": "key", 506 | "type": "key" 507 | } 508 | ], 509 | "since": "2.0.0", 510 | "group": "hash" 511 | }, 512 | "HINCRBY": { 513 | "summary": "Increment the integer value of a hash field by the given number", 514 | "complexity": "O(1)", 515 | "arguments": [ 516 | { 517 | "name": "key", 518 | "type": "key" 519 | }, 520 | { 521 | "name": "field", 522 | "type": "string" 523 | }, 524 | { 525 | "name": "increment", 526 | "type": "integer" 527 | } 528 | ], 529 | "since": "2.0.0", 530 | "group": "hash" 531 | }, 532 | "HINCRBYFLOAT": { 533 | "summary": "Increment the float value of a hash field by the given amount", 534 | "complexity": "O(1)", 535 | "arguments": [ 536 | { 537 | "name": "key", 538 | "type": "key" 539 | }, 540 | { 541 | "name": "field", 542 | "type": "string" 543 | }, 544 | { 545 | "name": "increment", 546 | "type": "double" 547 | } 548 | ], 549 | "since": "2.6.0", 550 | "group": "hash" 551 | }, 552 | "HKEYS": { 553 | "summary": "Get all the fields in a hash", 554 | "complexity": "O(N) where N is the size of the hash.", 555 | "arguments": [ 556 | { 557 | "name": "key", 558 | "type": "key" 559 | } 560 | ], 561 | "since": "2.0.0", 562 | "group": "hash" 563 | }, 564 | "HLEN": { 565 | "summary": "Get the number of fields in a hash", 566 | "complexity": "O(1)", 567 | "arguments": [ 568 | { 569 | "name": "key", 570 | "type": "key" 571 | } 572 | ], 573 | "since": "2.0.0", 574 | "group": "hash" 575 | }, 576 | "HMGET": { 577 | "summary": "Get the values of all the given hash fields", 578 | "complexity": "O(N) where N is the number of fields being requested.", 579 | "arguments": [ 580 | { 581 | "name": "key", 582 | "type": "key" 583 | }, 584 | { 585 | "name": "field", 586 | "type": "string", 587 | "multiple": true 588 | } 589 | ], 590 | "since": "2.0.0", 591 | "group": "hash" 592 | }, 593 | "HMSET": { 594 | "summary": "Set multiple hash fields to multiple values", 595 | "complexity": "O(N) where N is the number of fields being set.", 596 | "arguments": [ 597 | { 598 | "name": "key", 599 | "type": "key" 600 | }, 601 | { 602 | "name": ["field", "value"], 603 | "type": ["string", "string"], 604 | "multiple": true 605 | } 606 | ], 607 | "since": "2.0.0", 608 | "group": "hash" 609 | }, 610 | "HSET": { 611 | "summary": "Set the string value of a hash field", 612 | "complexity": "O(1)", 613 | "arguments": [ 614 | { 615 | "name": "key", 616 | "type": "key" 617 | }, 618 | { 619 | "name": "field", 620 | "type": "string" 621 | }, 622 | { 623 | "name": "value", 624 | "type": "string" 625 | } 626 | ], 627 | "since": "2.0.0", 628 | "group": "hash" 629 | }, 630 | "HSETNX": { 631 | "summary": "Set the value of a hash field, only if the field does not exist", 632 | "complexity": "O(1)", 633 | "arguments": [ 634 | { 635 | "name": "key", 636 | "type": "key" 637 | }, 638 | { 639 | "name": "field", 640 | "type": "string" 641 | }, 642 | { 643 | "name": "value", 644 | "type": "string" 645 | } 646 | ], 647 | "since": "2.0.0", 648 | "group": "hash" 649 | }, 650 | "HVALS": { 651 | "summary": "Get all the values in a hash", 652 | "complexity": "O(N) where N is the size of the hash.", 653 | "arguments": [ 654 | { 655 | "name": "key", 656 | "type": "key" 657 | } 658 | ], 659 | "since": "2.0.0", 660 | "group": "hash" 661 | }, 662 | "INCR": { 663 | "summary": "Increment the integer value of a key by one", 664 | "complexity": "O(1)", 665 | "arguments": [ 666 | { 667 | "name": "key", 668 | "type": "key" 669 | } 670 | ], 671 | "since": "1.0.0", 672 | "group": "string" 673 | }, 674 | "INCRBY": { 675 | "summary": "Increment the integer value of a key by the given amount", 676 | "complexity": "O(1)", 677 | "arguments": [ 678 | { 679 | "name": "key", 680 | "type": "key" 681 | }, 682 | { 683 | "name": "increment", 684 | "type": "integer" 685 | } 686 | ], 687 | "since": "1.0.0", 688 | "group": "string" 689 | }, 690 | "INCRBYFLOAT": { 691 | "summary": "Increment the float value of a key by the given amount", 692 | "complexity": "O(1)", 693 | "arguments": [ 694 | { 695 | "name": "key", 696 | "type": "key" 697 | }, 698 | { 699 | "name": "increment", 700 | "type": "double" 701 | } 702 | ], 703 | "since": "2.6.0", 704 | "group": "string" 705 | }, 706 | "INFO": { 707 | "summary": "Get information and statistics about the server", 708 | "since": "1.0.0", 709 | "group": "server" 710 | }, 711 | "KEYS": { 712 | "summary": "Find all keys matching the given pattern", 713 | "complexity": "O(N) with N being the number of keys in the database, under the assumption that the key names in the database and the given pattern have limited length.", 714 | "arguments": [ 715 | { 716 | "name": "pattern", 717 | "type": "pattern" 718 | } 719 | ], 720 | "since": "1.0.0", 721 | "group": "generic" 722 | }, 723 | "LASTSAVE": { 724 | "summary": "Get the UNIX time stamp of the last successful save to disk", 725 | "since": "1.0.0", 726 | "group": "server" 727 | }, 728 | "LINDEX": { 729 | "summary": "Get an element from a list by its index", 730 | "complexity": "O(N) where N is the number of elements to traverse to get to the element at index. This makes asking for the first or the last element of the list O(1).", 731 | "arguments": [ 732 | { 733 | "name": "key", 734 | "type": "key" 735 | }, 736 | { 737 | "name": "index", 738 | "type": "integer" 739 | } 740 | ], 741 | "since": "1.0.0", 742 | "group": "list" 743 | }, 744 | "LINSERT": { 745 | "summary": "Insert an element before or after another element in a list", 746 | "complexity": "O(N) where N is the number of elements to traverse before seeing the value pivot. This means that inserting somewhere on the left end on the list (head) can be considered O(1) and inserting somewhere on the right end (tail) is O(N).", 747 | "arguments": [ 748 | { 749 | "name": "key", 750 | "type": "key" 751 | }, 752 | { 753 | "name": "where", 754 | "type": "enum", 755 | "enum": ["BEFORE", "AFTER"] 756 | }, 757 | { 758 | "name": "pivot", 759 | "type": "string" 760 | }, 761 | { 762 | "name": "value", 763 | "type": "string" 764 | } 765 | ], 766 | "since": "2.2.0", 767 | "group": "list" 768 | }, 769 | "LLEN": { 770 | "summary": "Get the length of a list", 771 | "complexity": "O(1)", 772 | "arguments": [ 773 | { 774 | "name": "key", 775 | "type": "key" 776 | } 777 | ], 778 | "since": "1.0.0", 779 | "group": "list" 780 | }, 781 | "LPOP": { 782 | "summary": "Remove and get the first element in a list", 783 | "complexity": "O(1)", 784 | "arguments": [ 785 | { 786 | "name": "key", 787 | "type": "key" 788 | } 789 | ], 790 | "since": "1.0.0", 791 | "group": "list" 792 | }, 793 | "LPUSH": { 794 | "summary": "Prepend one or multiple values to a list", 795 | "complexity": "O(1)", 796 | "arguments": [ 797 | { 798 | "name": "key", 799 | "type": "key" 800 | }, 801 | { 802 | "name": "value", 803 | "type": "string", 804 | "multiple": true 805 | } 806 | ], 807 | "since": "1.0.0", 808 | "group": "list" 809 | }, 810 | "LPUSHX": { 811 | "summary": "Prepend a value to a list, only if the list exists", 812 | "complexity": "O(1)", 813 | "arguments": [ 814 | { 815 | "name": "key", 816 | "type": "key" 817 | }, 818 | { 819 | "name": "value", 820 | "type": "string" 821 | } 822 | ], 823 | "since": "2.2.0", 824 | "group": "list" 825 | }, 826 | "LRANGE": { 827 | "summary": "Get a range of elements from a list", 828 | "complexity": "O(S+N) where S is the start offset and N is the number of elements in the specified range.", 829 | "arguments": [ 830 | { 831 | "name": "key", 832 | "type": "key" 833 | }, 834 | { 835 | "name": "start", 836 | "type": "integer" 837 | }, 838 | { 839 | "name": "stop", 840 | "type": "integer" 841 | } 842 | ], 843 | "since": "1.0.0", 844 | "group": "list" 845 | }, 846 | "LREM": { 847 | "summary": "Remove elements from a list", 848 | "complexity": "O(N) where N is the length of the list.", 849 | "arguments": [ 850 | { 851 | "name": "key", 852 | "type": "key" 853 | }, 854 | { 855 | "name": "count", 856 | "type": "integer" 857 | }, 858 | { 859 | "name": "value", 860 | "type": "string" 861 | } 862 | ], 863 | "since": "1.0.0", 864 | "group": "list" 865 | }, 866 | "LSET": { 867 | "summary": "Set the value of an element in a list by its index", 868 | "complexity": "O(N) where N is the length of the list. Setting either the first or the last element of the list is O(1).", 869 | "arguments": [ 870 | { 871 | "name": "key", 872 | "type": "key" 873 | }, 874 | { 875 | "name": "index", 876 | "type": "integer" 877 | }, 878 | { 879 | "name": "value", 880 | "type": "string" 881 | } 882 | ], 883 | "since": "1.0.0", 884 | "group": "list" 885 | }, 886 | "LTRIM": { 887 | "summary": "Trim a list to the specified range", 888 | "complexity": "O(N) where N is the number of elements to be removed by the operation.", 889 | "arguments": [ 890 | { 891 | "name": "key", 892 | "type": "key" 893 | }, 894 | { 895 | "name": "start", 896 | "type": "integer" 897 | }, 898 | { 899 | "name": "stop", 900 | "type": "integer" 901 | } 902 | ], 903 | "since": "1.0.0", 904 | "group": "list" 905 | }, 906 | "MGET": { 907 | "summary": "Get the values of all the given keys", 908 | "complexity": "O(N) where N is the number of keys to retrieve.", 909 | "arguments": [ 910 | { 911 | "name": "key", 912 | "type": "key", 913 | "multiple": true 914 | } 915 | ], 916 | "since": "1.0.0", 917 | "group": "string" 918 | }, 919 | "MIGRATE": { 920 | "summary": "Atomically transfer a key from a Redis instance to another one.", 921 | "complexity": "This command actually executes a DUMP+DEL in the source instance, and a RESTORE in the target instance. See the pages of these commands for time complexity. Also an O(N) data transfer between the two instances is performed.", 922 | "arguments": [ 923 | { 924 | "name": "host", 925 | "type": "string" 926 | }, 927 | { 928 | "name": "port", 929 | "type": "string" 930 | }, 931 | { 932 | "name": "key", 933 | "type": "key" 934 | }, 935 | { 936 | "name": "destination-db", 937 | "type": "integer" 938 | }, 939 | { 940 | "name": "timeout", 941 | "type": "integer" 942 | } 943 | ], 944 | "since": "2.6.0", 945 | "group": "generic" 946 | }, 947 | "MONITOR": { 948 | "summary": "Listen for all requests received by the server in real time", 949 | "since": "1.0.0", 950 | "group": "server" 951 | }, 952 | "MOVE": { 953 | "summary": "Move a key to another database", 954 | "complexity": "O(1)", 955 | "arguments": [ 956 | { 957 | "name": "key", 958 | "type": "key" 959 | }, 960 | { 961 | "name": "db", 962 | "type": "integer" 963 | } 964 | ], 965 | "since": "1.0.0", 966 | "group": "generic" 967 | }, 968 | "MSET": { 969 | "summary": "Set multiple keys to multiple values", 970 | "complexity": "O(N) where N is the number of keys to set.", 971 | "arguments": [ 972 | { 973 | "name": ["key", "value"], 974 | "type": ["key", "string"], 975 | "multiple": true 976 | } 977 | ], 978 | "since": "1.0.1", 979 | "group": "string" 980 | }, 981 | "MSETNX": { 982 | "summary": "Set multiple keys to multiple values, only if none of the keys exist", 983 | "complexity": "O(N) where N is the number of keys to set.", 984 | "arguments": [ 985 | { 986 | "name": ["key", "value"], 987 | "type": ["key", "string"], 988 | "multiple": true 989 | } 990 | ], 991 | "since": "1.0.1", 992 | "group": "string" 993 | }, 994 | "MULTI": { 995 | "summary": "Mark the start of a transaction block", 996 | "since": "1.2.0", 997 | "group": "transactions" 998 | }, 999 | "OBJECT": { 1000 | "summary": "Inspect the internals of Redis objects", 1001 | "complexity": "O(1) for all the currently implemented subcommands.", 1002 | "since": "2.2.3", 1003 | "group": "generic", 1004 | "arguments": [ 1005 | { 1006 | "name": "subcommand", 1007 | "type": "string" 1008 | }, 1009 | { 1010 | "name": "arguments", 1011 | "type": "string", 1012 | "optional": true, 1013 | "multiple": true 1014 | } 1015 | ] 1016 | }, 1017 | "PERSIST": { 1018 | "summary": "Remove the expiration from a key", 1019 | "complexity": "O(1)", 1020 | "arguments": [ 1021 | { 1022 | "name": "key", 1023 | "type": "key" 1024 | } 1025 | ], 1026 | "since": "2.2.0", 1027 | "group": "generic" 1028 | }, 1029 | "PEXPIRE": { 1030 | "summary": "Set a key's time to live in milliseconds", 1031 | "complexity": "O(1)", 1032 | "arguments": [ 1033 | { 1034 | "name": "key", 1035 | "type": "key" 1036 | }, 1037 | { 1038 | "name": "milliseconds", 1039 | "type": "integer" 1040 | } 1041 | ], 1042 | "since": "2.6.0", 1043 | "group": "generic" 1044 | }, 1045 | "PEXPIREAT": { 1046 | "summary": "Set the expiration for a key as a UNIX timestamp specified in milliseconds", 1047 | "complexity": "O(1)", 1048 | "arguments": [ 1049 | { 1050 | "name": "key", 1051 | "type": "key" 1052 | }, 1053 | { 1054 | "name": "milliseconds-timestamp", 1055 | "type": "posix time" 1056 | } 1057 | ], 1058 | "since": "2.6.0", 1059 | "group": "generic" 1060 | }, 1061 | "PING": { 1062 | "summary": "Ping the server", 1063 | "since": "1.0.0", 1064 | "group": "connection" 1065 | }, 1066 | "PSETEX": { 1067 | "summary": "Set the value and expiration in milliseconds of a key", 1068 | "complexity": "O(1)", 1069 | "arguments": [ 1070 | { 1071 | "name": "key", 1072 | "type": "key" 1073 | }, 1074 | { 1075 | "name": "milliseconds", 1076 | "type": "integer" 1077 | }, 1078 | { 1079 | "name": "value", 1080 | "type": "string" 1081 | } 1082 | ], 1083 | "since": "2.6.0", 1084 | "group": "string" 1085 | }, 1086 | "PSUBSCRIBE": { 1087 | "summary": "Listen for messages published to channels matching the given patterns", 1088 | "complexity": "O(N) where N is the number of patterns the client is already subscribed to.", 1089 | "arguments": [ 1090 | { 1091 | "name": ["pattern"], 1092 | "type": ["pattern"], 1093 | "multiple": true 1094 | } 1095 | ], 1096 | "since": "2.0.0", 1097 | "group": "pubsub" 1098 | }, 1099 | "PTTL": { 1100 | "summary": "Get the time to live for a key in milliseconds", 1101 | "complexity": "O(1)", 1102 | "arguments": [ 1103 | { 1104 | "name": "key", 1105 | "type": "key" 1106 | } 1107 | ], 1108 | "since": "2.6.0", 1109 | "group": "generic" 1110 | }, 1111 | "PUBLISH": { 1112 | "summary": "Post a message to a channel", 1113 | "complexity": "O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).", 1114 | "arguments": [ 1115 | { 1116 | "name": "channel", 1117 | "type": "string" 1118 | }, 1119 | { 1120 | "name": "message", 1121 | "type": "string" 1122 | } 1123 | ], 1124 | "since": "2.0.0", 1125 | "group": "pubsub" 1126 | }, 1127 | "PUNSUBSCRIBE": { 1128 | "summary": "Stop listening for messages posted to channels matching the given patterns", 1129 | "complexity": "O(N+M) where N is the number of patterns the client is already subscribed and M is the number of total patterns subscribed in the system (by any client).", 1130 | "arguments": [ 1131 | { 1132 | "name": "pattern", 1133 | "type": "pattern", 1134 | "optional": true, 1135 | "multiple": true 1136 | } 1137 | ], 1138 | "since": "2.0.0", 1139 | "group": "pubsub" 1140 | }, 1141 | "QUIT": { 1142 | "summary": "Close the connection", 1143 | "since": "1.0.0", 1144 | "group": "connection" 1145 | }, 1146 | "RANDOMKEY": { 1147 | "summary": "Return a random key from the keyspace", 1148 | "complexity": "O(1)", 1149 | "since": "1.0.0", 1150 | "group": "generic" 1151 | }, 1152 | "RENAME": { 1153 | "summary": "Rename a key", 1154 | "complexity": "O(1)", 1155 | "arguments": [ 1156 | { 1157 | "name": "key", 1158 | "type": "key" 1159 | }, 1160 | { 1161 | "name": "newkey", 1162 | "type": "key" 1163 | } 1164 | ], 1165 | "since": "1.0.0", 1166 | "group": "generic" 1167 | }, 1168 | "RENAMENX": { 1169 | "summary": "Rename a key, only if the new key does not exist", 1170 | "complexity": "O(1)", 1171 | "arguments": [ 1172 | { 1173 | "name": "key", 1174 | "type": "key" 1175 | }, 1176 | { 1177 | "name": "newkey", 1178 | "type": "key" 1179 | } 1180 | ], 1181 | "since": "1.0.0", 1182 | "group": "generic" 1183 | }, 1184 | "RESTORE": { 1185 | "summary": "Create a key using the provided serialized value, previously obtained using DUMP.", 1186 | "complexity": "O(1) to create the new key and additional O(N*M) to recostruct the serialized value, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1). However for sorted set values the complexity is O(N*M*log(N)) because inserting values into sorted sets is O(log(N)).", 1187 | "arguments": [ 1188 | { 1189 | "name": "key", 1190 | "type": "key" 1191 | }, 1192 | { 1193 | "name": "ttl", 1194 | "type": "integer" 1195 | }, 1196 | { 1197 | "name": "serialized-value", 1198 | "type": "string" 1199 | } 1200 | ], 1201 | "since": "2.6.0", 1202 | "group": "generic" 1203 | }, 1204 | "RPOP": { 1205 | "summary": "Remove and get the last element in a list", 1206 | "complexity": "O(1)", 1207 | "arguments": [ 1208 | { 1209 | "name": "key", 1210 | "type": "key" 1211 | } 1212 | ], 1213 | "since": "1.0.0", 1214 | "group": "list" 1215 | }, 1216 | "RPOPLPUSH": { 1217 | "summary": "Remove the last element in a list, append it to another list and return it", 1218 | "complexity": "O(1)", 1219 | "arguments": [ 1220 | { 1221 | "name": "source", 1222 | "type": "key" 1223 | }, 1224 | { 1225 | "name": "destination", 1226 | "type": "key" 1227 | } 1228 | ], 1229 | "since": "1.2.0", 1230 | "group": "list" 1231 | }, 1232 | "RPUSH": { 1233 | "summary": "Append one or multiple values to a list", 1234 | "complexity": "O(1)", 1235 | "arguments": [ 1236 | { 1237 | "name": "key", 1238 | "type": "key" 1239 | }, 1240 | { 1241 | "name": "value", 1242 | "type": "string", 1243 | "multiple": true 1244 | } 1245 | ], 1246 | "since": "1.0.0", 1247 | "group": "list" 1248 | }, 1249 | "RPUSHX": { 1250 | "summary": "Append a value to a list, only if the list exists", 1251 | "complexity": "O(1)", 1252 | "arguments": [ 1253 | { 1254 | "name": "key", 1255 | "type": "key" 1256 | }, 1257 | { 1258 | "name": "value", 1259 | "type": "string" 1260 | } 1261 | ], 1262 | "since": "2.2.0", 1263 | "group": "list" 1264 | }, 1265 | "SADD": { 1266 | "summary": "Add one or more members to a set", 1267 | "complexity": "O(N) where N is the number of members to be added.", 1268 | "arguments": [ 1269 | { 1270 | "name": "key", 1271 | "type": "key" 1272 | }, 1273 | { 1274 | "name": "member", 1275 | "type": "string", 1276 | "multiple": true 1277 | } 1278 | ], 1279 | "since": "1.0.0", 1280 | "group": "set" 1281 | }, 1282 | "SAVE": { 1283 | "summary": "Synchronously save the dataset to disk", 1284 | "since": "1.0.0", 1285 | "group": "server" 1286 | }, 1287 | "SCARD": { 1288 | "summary": "Get the number of members in a set", 1289 | "complexity": "O(1)", 1290 | "arguments": [ 1291 | { 1292 | "name": "key", 1293 | "type": "key" 1294 | } 1295 | ], 1296 | "since": "1.0.0", 1297 | "group": "set" 1298 | }, 1299 | "SCRIPT EXISTS": { 1300 | "summary": "Check existence of scripts in the script cache.", 1301 | "complexity": "O(N) with N being the number of scripts to check (so checking a single script is an O(1) operation).", 1302 | "arguments": [ 1303 | { 1304 | "name": "script", 1305 | "type": "string", 1306 | "multiple": true 1307 | } 1308 | ], 1309 | "since": "2.6.0", 1310 | "group": "scripting" 1311 | }, 1312 | "SCRIPT FLUSH": { 1313 | "summary": "Remove all the scripts from the script cache.", 1314 | "complexity": "O(N) with N being the number of scripts in cache", 1315 | "since": "2.6.0", 1316 | "group": "scripting" 1317 | }, 1318 | "SCRIPT KILL": { 1319 | "summary": "Kill the script currently in execution.", 1320 | "complexity": "O(1)", 1321 | "since": "2.6.0", 1322 | "group": "scripting" 1323 | }, 1324 | "SCRIPT LOAD": { 1325 | "summary": "Load the specified Lua script into the script cache.", 1326 | "complexity": "O(N) with N being the length in bytes of the script body.", 1327 | "arguments": [ 1328 | { 1329 | "name": "script", 1330 | "type": "string" 1331 | } 1332 | ], 1333 | "since": "2.6.0", 1334 | "group": "scripting" 1335 | }, 1336 | "SDIFF": { 1337 | "summary": "Subtract multiple sets", 1338 | "complexity": "O(N) where N is the total number of elements in all given sets.", 1339 | "arguments": [ 1340 | { 1341 | "name": "key", 1342 | "type": "key", 1343 | "multiple": true 1344 | } 1345 | ], 1346 | "since": "1.0.0", 1347 | "group": "set" 1348 | }, 1349 | "SDIFFSTORE": { 1350 | "summary": "Subtract multiple sets and store the resulting set in a key", 1351 | "complexity": "O(N) where N is the total number of elements in all given sets.", 1352 | "arguments": [ 1353 | { 1354 | "name": "destination", 1355 | "type": "key" 1356 | }, 1357 | { 1358 | "name": "key", 1359 | "type": "key", 1360 | "multiple": true 1361 | } 1362 | ], 1363 | "since": "1.0.0", 1364 | "group": "set" 1365 | }, 1366 | "SELECT": { 1367 | "summary": "Change the selected database for the current connection", 1368 | "arguments": [ 1369 | { 1370 | "name": "index", 1371 | "type": "integer" 1372 | } 1373 | ], 1374 | "since": "1.0.0", 1375 | "group": "connection" 1376 | }, 1377 | "SET": { 1378 | "summary": "Set the string value of a key", 1379 | "complexity": "O(1)", 1380 | "arguments": [ 1381 | { 1382 | "name": "key", 1383 | "type": "key" 1384 | }, 1385 | { 1386 | "name": "value", 1387 | "type": "string" 1388 | } 1389 | ], 1390 | "since": "1.0.0", 1391 | "group": "string" 1392 | }, 1393 | "SETBIT": { 1394 | "summary": "Sets or clears the bit at offset in the string value stored at key", 1395 | "complexity": "O(1)", 1396 | "arguments": [ 1397 | { 1398 | "name": "key", 1399 | "type": "key" 1400 | }, 1401 | { 1402 | "name": "offset", 1403 | "type": "integer" 1404 | }, 1405 | { 1406 | "name": "value", 1407 | "type": "string" 1408 | } 1409 | ], 1410 | "since": "2.2.0", 1411 | "group": "string" 1412 | }, 1413 | "SETEX": { 1414 | "summary": "Set the value and expiration of a key", 1415 | "complexity": "O(1)", 1416 | "arguments": [ 1417 | { 1418 | "name": "key", 1419 | "type": "key" 1420 | }, 1421 | { 1422 | "name": "seconds", 1423 | "type": "integer" 1424 | }, 1425 | { 1426 | "name": "value", 1427 | "type": "string" 1428 | } 1429 | ], 1430 | "since": "2.0.0", 1431 | "group": "string" 1432 | }, 1433 | "SETNX": { 1434 | "summary": "Set the value of a key, only if the key does not exist", 1435 | "complexity": "O(1)", 1436 | "arguments": [ 1437 | { 1438 | "name": "key", 1439 | "type": "key" 1440 | }, 1441 | { 1442 | "name": "value", 1443 | "type": "string" 1444 | } 1445 | ], 1446 | "since": "1.0.0", 1447 | "group": "string" 1448 | }, 1449 | "SETRANGE": { 1450 | "summary": "Overwrite part of a string at key starting at the specified offset", 1451 | "complexity": "O(1), not counting the time taken to copy the new string in place. Usually, this string is very small so the amortized complexity is O(1). Otherwise, complexity is O(M) with M being the length of the value argument.", 1452 | "arguments": [ 1453 | { 1454 | "name": "key", 1455 | "type": "key" 1456 | }, 1457 | { 1458 | "name": "offset", 1459 | "type": "integer" 1460 | }, 1461 | { 1462 | "name": "value", 1463 | "type": "string" 1464 | } 1465 | ], 1466 | "since": "2.2.0", 1467 | "group": "string" 1468 | }, 1469 | "SHUTDOWN": { 1470 | "summary": "Synchronously save the dataset to disk and then shut down the server", 1471 | "arguments": [ 1472 | { 1473 | "name": "NOSAVE", 1474 | "type": "enum", 1475 | "enum": ["NOSAVE"], 1476 | "optional": true 1477 | }, 1478 | { 1479 | "name": "SAVE", 1480 | "type": "enum", 1481 | "enum": ["SAVE"], 1482 | "optional": true 1483 | } 1484 | ], 1485 | "since": "1.0.0", 1486 | "group": "server" 1487 | }, 1488 | "SINTER": { 1489 | "summary": "Intersect multiple sets", 1490 | "complexity": "O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.", 1491 | "arguments": [ 1492 | { 1493 | "name": "key", 1494 | "type": "key", 1495 | "multiple": true 1496 | } 1497 | ], 1498 | "since": "1.0.0", 1499 | "group": "set" 1500 | }, 1501 | "SINTERSTORE": { 1502 | "summary": "Intersect multiple sets and store the resulting set in a key", 1503 | "complexity": "O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.", 1504 | "arguments": [ 1505 | { 1506 | "name": "destination", 1507 | "type": "key" 1508 | }, 1509 | { 1510 | "name": "key", 1511 | "type": "key", 1512 | "multiple": true 1513 | } 1514 | ], 1515 | "since": "1.0.0", 1516 | "group": "set" 1517 | }, 1518 | "SISMEMBER": { 1519 | "summary": "Determine if a given value is a member of a set", 1520 | "complexity": "O(1)", 1521 | "arguments": [ 1522 | { 1523 | "name": "key", 1524 | "type": "key" 1525 | }, 1526 | { 1527 | "name": "member", 1528 | "type": "string" 1529 | } 1530 | ], 1531 | "since": "1.0.0", 1532 | "group": "set" 1533 | }, 1534 | "SLAVEOF": { 1535 | "summary": "Make the server a slave of another instance, or promote it as master", 1536 | "arguments": [ 1537 | { 1538 | "name": "host", 1539 | "type": "string" 1540 | }, 1541 | { 1542 | "name": "port", 1543 | "type": "string" 1544 | } 1545 | ], 1546 | "since": "1.0.0", 1547 | "group": "server" 1548 | }, 1549 | "SLOWLOG": { 1550 | "summary": "Manages the Redis slow queries log", 1551 | "arguments": [ 1552 | { 1553 | "name": "subcommand", 1554 | "type": "string" 1555 | }, 1556 | { 1557 | "name": "argument", 1558 | "type": "string", 1559 | "optional": true 1560 | } 1561 | ], 1562 | "since": "2.2.12", 1563 | "group": "server" 1564 | }, 1565 | "SMEMBERS": { 1566 | "summary": "Get all the members in a set", 1567 | "complexity": "O(N) where N is the set cardinality.", 1568 | "arguments": [ 1569 | { 1570 | "name": "key", 1571 | "type": "key" 1572 | } 1573 | ], 1574 | "since": "1.0.0", 1575 | "group": "set" 1576 | }, 1577 | "SMOVE": { 1578 | "summary": "Move a member from one set to another", 1579 | "complexity": "O(1)", 1580 | "arguments": [ 1581 | { 1582 | "name": "source", 1583 | "type": "key" 1584 | }, 1585 | { 1586 | "name": "destination", 1587 | "type": "key" 1588 | }, 1589 | { 1590 | "name": "member", 1591 | "type": "string" 1592 | } 1593 | ], 1594 | "since": "1.0.0", 1595 | "group": "set" 1596 | }, 1597 | "SORT": { 1598 | "summary": "Sort the elements in a list, set or sorted set", 1599 | "complexity": "O(N+M*log(M)) where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is currently O(N) as there is a copy step that will be avoided in next releases.", 1600 | "arguments": [ 1601 | { 1602 | "name": "key", 1603 | "type": "key" 1604 | }, 1605 | { 1606 | "command": "BY", 1607 | "name": "pattern", 1608 | "type": "pattern", 1609 | "optional": true 1610 | }, 1611 | { 1612 | "command": "LIMIT", 1613 | "name": ["offset", "count"], 1614 | "type": ["integer", "integer"], 1615 | "optional": true 1616 | }, 1617 | { 1618 | "command": "GET", 1619 | "name": "pattern", 1620 | "type": "string", 1621 | "optional": true, 1622 | "multiple": true 1623 | }, 1624 | { 1625 | "name": "order", 1626 | "type": "enum", 1627 | "enum": ["ASC", "DESC"], 1628 | "optional": true 1629 | }, 1630 | { 1631 | "name": "sorting", 1632 | "type": "enum", 1633 | "enum": ["ALPHA"], 1634 | "optional": true 1635 | }, 1636 | { 1637 | "command": "STORE", 1638 | "name": "destination", 1639 | "type": "key", 1640 | "optional": true 1641 | } 1642 | ], 1643 | "since": "1.0.0", 1644 | "group": "generic" 1645 | }, 1646 | "SPOP": { 1647 | "summary": "Remove and return a random member from a set", 1648 | "complexity": "O(1)", 1649 | "arguments": [ 1650 | { 1651 | "name": "key", 1652 | "type": "key" 1653 | } 1654 | ], 1655 | "since": "1.0.0", 1656 | "group": "set" 1657 | }, 1658 | "SRANDMEMBER": { 1659 | "summary": "Get one or multiple random members from a set", 1660 | "complexity": "Without the count argument O(1), otherwise O(N) where N is the absolute value of the passed count.", 1661 | "arguments": [ 1662 | { 1663 | "name": "key", 1664 | "type": "key" 1665 | }, 1666 | { 1667 | "name": "count", 1668 | "type": "integer", 1669 | "optional": true 1670 | } 1671 | ], 1672 | "since": "1.0.0", 1673 | "group": "set" 1674 | }, 1675 | "SREM": { 1676 | "summary": "Remove one or more members from a set", 1677 | "complexity": "O(N) where N is the number of members to be removed.", 1678 | "arguments": [ 1679 | { 1680 | "name": "key", 1681 | "type": "key" 1682 | }, 1683 | { 1684 | "name": "member", 1685 | "type": "string", 1686 | "multiple": true 1687 | } 1688 | ], 1689 | "since": "1.0.0", 1690 | "group": "set" 1691 | }, 1692 | "STRLEN": { 1693 | "summary": "Get the length of the value stored in a key", 1694 | "complexity": "O(1)", 1695 | "arguments": [ 1696 | { 1697 | "name": "key", 1698 | "type": "key" 1699 | } 1700 | ], 1701 | "since": "2.2.0", 1702 | "group": "string" 1703 | }, 1704 | "SUBSCRIBE": { 1705 | "summary": "Listen for messages published to the given channels", 1706 | "complexity": "O(N) where N is the number of channels to subscribe to.", 1707 | "arguments": [ 1708 | { 1709 | "name": ["channel"], 1710 | "type": ["string"], 1711 | "multiple": true 1712 | } 1713 | ], 1714 | "since": "2.0.0", 1715 | "group": "pubsub" 1716 | }, 1717 | "SUNION": { 1718 | "summary": "Add multiple sets", 1719 | "complexity": "O(N) where N is the total number of elements in all given sets.", 1720 | "arguments": [ 1721 | { 1722 | "name": "key", 1723 | "type": "key", 1724 | "multiple": true 1725 | } 1726 | ], 1727 | "since": "1.0.0", 1728 | "group": "set" 1729 | }, 1730 | "SUNIONSTORE": { 1731 | "summary": "Add multiple sets and store the resulting set in a key", 1732 | "complexity": "O(N) where N is the total number of elements in all given sets.", 1733 | "arguments": [ 1734 | { 1735 | "name": "destination", 1736 | "type": "key" 1737 | }, 1738 | { 1739 | "name": "key", 1740 | "type": "key", 1741 | "multiple": true 1742 | } 1743 | ], 1744 | "since": "1.0.0", 1745 | "group": "set" 1746 | }, 1747 | "SYNC": { 1748 | "summary": "Internal command used for replication", 1749 | "since": "1.0.0", 1750 | "group": "server" 1751 | }, 1752 | "TIME": { 1753 | "summary": "Return the current server time", 1754 | "complexity": "O(1)", 1755 | "since": "2.6.0", 1756 | "group": "server" 1757 | }, 1758 | "TTL": { 1759 | "summary": "Get the time to live for a key", 1760 | "complexity": "O(1)", 1761 | "arguments": [ 1762 | { 1763 | "name": "key", 1764 | "type": "key" 1765 | } 1766 | ], 1767 | "since": "1.0.0", 1768 | "group": "generic" 1769 | }, 1770 | "TYPE": { 1771 | "summary": "Determine the type stored at key", 1772 | "complexity": "O(1)", 1773 | "arguments": [ 1774 | { 1775 | "name": "key", 1776 | "type": "key" 1777 | } 1778 | ], 1779 | "since": "1.0.0", 1780 | "group": "generic" 1781 | }, 1782 | "UNSUBSCRIBE": { 1783 | "summary": "Stop listening for messages posted to the given channels", 1784 | "complexity": "O(N) where N is the number of clients already subscribed to a channel.", 1785 | "arguments": [ 1786 | { 1787 | "name": "channel", 1788 | "type": "string", 1789 | "optional": true, 1790 | "multiple": true 1791 | } 1792 | ], 1793 | "since": "2.0.0", 1794 | "group": "pubsub" 1795 | }, 1796 | "UNWATCH": { 1797 | "summary": "Forget about all watched keys", 1798 | "complexity": "O(1)", 1799 | "since": "2.2.0", 1800 | "group": "transactions" 1801 | }, 1802 | "WATCH": { 1803 | "summary": "Watch the given keys to determine execution of the MULTI/EXEC block", 1804 | "complexity": "O(1) for every key.", 1805 | "arguments": [ 1806 | { 1807 | "name": "key", 1808 | "type": "key", 1809 | "multiple": true 1810 | } 1811 | ], 1812 | "since": "2.2.0", 1813 | "group": "transactions" 1814 | }, 1815 | "ZADD": { 1816 | "summary": "Add one or more members to a sorted set, or update its score if it already exists", 1817 | "complexity": "O(log(N)) where N is the number of elements in the sorted set.", 1818 | "arguments": [ 1819 | { 1820 | "name": "key", 1821 | "type": "key" 1822 | }, 1823 | { 1824 | "name": ["score", "member"], 1825 | "type": ["double", "string"], 1826 | "multiple": true 1827 | } 1828 | ], 1829 | "since": "1.2.0", 1830 | "group": "sorted_set" 1831 | }, 1832 | "ZCARD": { 1833 | "summary": "Get the number of members in a sorted set", 1834 | "complexity": "O(1)", 1835 | "arguments": [ 1836 | { 1837 | "name": "key", 1838 | "type": "key" 1839 | } 1840 | ], 1841 | "since": "1.2.0", 1842 | "group": "sorted_set" 1843 | }, 1844 | "ZCOUNT": { 1845 | "summary": "Count the members in a sorted set with scores within the given values", 1846 | "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M being the number of elements between min and max.", 1847 | "arguments": [ 1848 | { 1849 | "name": "key", 1850 | "type": "key" 1851 | }, 1852 | { 1853 | "name": "min", 1854 | "type": "double" 1855 | }, 1856 | { 1857 | "name": "max", 1858 | "type": "double" 1859 | } 1860 | ], 1861 | "since": "2.0.0", 1862 | "group": "sorted_set" 1863 | }, 1864 | "ZINCRBY": { 1865 | "summary": "Increment the score of a member in a sorted set", 1866 | "complexity": "O(log(N)) where N is the number of elements in the sorted set.", 1867 | "arguments": [ 1868 | { 1869 | "name": "key", 1870 | "type": "key" 1871 | }, 1872 | { 1873 | "name": "increment", 1874 | "type": "integer" 1875 | }, 1876 | { 1877 | "name": "member", 1878 | "type": "string" 1879 | } 1880 | ], 1881 | "since": "1.2.0", 1882 | "group": "sorted_set" 1883 | }, 1884 | "ZINTERSTORE": { 1885 | "summary": "Intersect multiple sorted sets and store the resulting sorted set in a new key", 1886 | "complexity": "O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.", 1887 | "arguments": [ 1888 | { 1889 | "name": "destination", 1890 | "type": "key" 1891 | }, 1892 | { 1893 | "name": "numkeys", 1894 | "type": "integer" 1895 | }, 1896 | { 1897 | "name": "key", 1898 | "type": "key", 1899 | "multiple": true 1900 | }, 1901 | { 1902 | "command": "WEIGHTS", 1903 | "name": "weight", 1904 | "type": "integer", 1905 | "variadic": true, 1906 | "optional": true 1907 | }, 1908 | { 1909 | "command": "AGGREGATE", 1910 | "name": "aggregate", 1911 | "type": "enum", 1912 | "enum": ["SUM", "MIN", "MAX"], 1913 | "optional": true 1914 | } 1915 | ], 1916 | "since": "2.0.0", 1917 | "group": "sorted_set" 1918 | }, 1919 | "ZRANGE": { 1920 | "summary": "Return a range of members in a sorted set, by index", 1921 | "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements returned.", 1922 | "arguments": [ 1923 | { 1924 | "name": "key", 1925 | "type": "key" 1926 | }, 1927 | { 1928 | "name": "start", 1929 | "type": "integer" 1930 | }, 1931 | { 1932 | "name": "stop", 1933 | "type": "integer" 1934 | }, 1935 | { 1936 | "name": "withscores", 1937 | "type": "enum", 1938 | "enum": ["WITHSCORES"], 1939 | "optional": true 1940 | } 1941 | ], 1942 | "since": "1.2.0", 1943 | "group": "sorted_set" 1944 | }, 1945 | "ZRANGEBYSCORE": { 1946 | "summary": "Return a range of members in a sorted set, by score", 1947 | "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", 1948 | "arguments": [ 1949 | { 1950 | "name": "key", 1951 | "type": "key" 1952 | }, 1953 | { 1954 | "name": "min", 1955 | "type": "double" 1956 | }, 1957 | { 1958 | "name": "max", 1959 | "type": "double" 1960 | }, 1961 | { 1962 | "name": "withscores", 1963 | "type": "enum", 1964 | "enum": ["WITHSCORES"], 1965 | "optional": true 1966 | }, 1967 | { 1968 | "command": "LIMIT", 1969 | "name": ["offset", "count"], 1970 | "type": ["integer", "integer"], 1971 | "optional": true 1972 | } 1973 | ], 1974 | "since": "1.0.5", 1975 | "group": "sorted_set" 1976 | }, 1977 | "ZRANK": { 1978 | "summary": "Determine the index of a member in a sorted set", 1979 | "complexity": "O(log(N))", 1980 | "arguments": [ 1981 | { 1982 | "name": "key", 1983 | "type": "key" 1984 | }, 1985 | { 1986 | "name": "member", 1987 | "type": "string" 1988 | } 1989 | ], 1990 | "since": "2.0.0", 1991 | "group": "sorted_set" 1992 | }, 1993 | "ZREM": { 1994 | "summary": "Remove one or more members from a sorted set", 1995 | "complexity": "O(M*log(N)) with N being the number of elements in the sorted set and M the number of elements to be removed.", 1996 | "arguments": [ 1997 | { 1998 | "name": "key", 1999 | "type": "key" 2000 | }, 2001 | { 2002 | "name": "member", 2003 | "type": "string", 2004 | "multiple": true 2005 | } 2006 | ], 2007 | "since": "1.2.0", 2008 | "group": "sorted_set" 2009 | }, 2010 | "ZREMRANGEBYRANK": { 2011 | "summary": "Remove all members in a sorted set within the given indexes", 2012 | "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.", 2013 | "arguments": [ 2014 | { 2015 | "name": "key", 2016 | "type": "key" 2017 | }, 2018 | { 2019 | "name": "start", 2020 | "type": "integer" 2021 | }, 2022 | { 2023 | "name": "stop", 2024 | "type": "integer" 2025 | } 2026 | ], 2027 | "since": "2.0.0", 2028 | "group": "sorted_set" 2029 | }, 2030 | "ZREMRANGEBYSCORE": { 2031 | "summary": "Remove all members in a sorted set within the given scores", 2032 | "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.", 2033 | "arguments": [ 2034 | { 2035 | "name": "key", 2036 | "type": "key" 2037 | }, 2038 | { 2039 | "name": "min", 2040 | "type": "double" 2041 | }, 2042 | { 2043 | "name": "max", 2044 | "type": "double" 2045 | } 2046 | ], 2047 | "since": "1.2.0", 2048 | "group": "sorted_set" 2049 | }, 2050 | "ZREVRANGE": { 2051 | "summary": "Return a range of members in a sorted set, by index, with scores ordered from high to low", 2052 | "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements returned.", 2053 | "arguments": [ 2054 | { 2055 | "name": "key", 2056 | "type": "key" 2057 | }, 2058 | { 2059 | "name": "start", 2060 | "type": "integer" 2061 | }, 2062 | { 2063 | "name": "stop", 2064 | "type": "integer" 2065 | }, 2066 | { 2067 | "name": "withscores", 2068 | "type": "enum", 2069 | "enum": ["WITHSCORES"], 2070 | "optional": true 2071 | } 2072 | ], 2073 | "since": "1.2.0", 2074 | "group": "sorted_set" 2075 | }, 2076 | "ZREVRANGEBYSCORE": { 2077 | "summary": "Return a range of members in a sorted set, by score, with scores ordered from high to low", 2078 | "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", 2079 | "arguments": [ 2080 | { 2081 | "name": "key", 2082 | "type": "key" 2083 | }, 2084 | { 2085 | "name": "max", 2086 | "type": "double" 2087 | }, 2088 | { 2089 | "name": "min", 2090 | "type": "double" 2091 | }, 2092 | { 2093 | "name": "withscores", 2094 | "type": "enum", 2095 | "enum": ["WITHSCORES"], 2096 | "optional": true 2097 | }, 2098 | { 2099 | "command": "LIMIT", 2100 | "name": ["offset", "count"], 2101 | "type": ["integer", "integer"], 2102 | "optional": true 2103 | } 2104 | ], 2105 | "since": "2.2.0", 2106 | "group": "sorted_set" 2107 | }, 2108 | "ZREVRANK": { 2109 | "summary": "Determine the index of a member in a sorted set, with scores ordered from high to low", 2110 | "complexity": "O(log(N))", 2111 | "arguments": [ 2112 | { 2113 | "name": "key", 2114 | "type": "key" 2115 | }, 2116 | { 2117 | "name": "member", 2118 | "type": "string" 2119 | } 2120 | ], 2121 | "since": "2.0.0", 2122 | "group": "sorted_set" 2123 | }, 2124 | "ZSCORE": { 2125 | "summary": "Get the score associated with the given member in a sorted set", 2126 | "complexity": "O(1)", 2127 | "arguments": [ 2128 | { 2129 | "name": "key", 2130 | "type": "key" 2131 | }, 2132 | { 2133 | "name": "member", 2134 | "type": "string" 2135 | } 2136 | ], 2137 | "since": "1.2.0", 2138 | "group": "sorted_set" 2139 | }, 2140 | "ZUNIONSTORE": { 2141 | "summary": "Add multiple sorted sets and store the resulting sorted set in a new key", 2142 | "complexity": "O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.", 2143 | "arguments": [ 2144 | { 2145 | "name": "destination", 2146 | "type": "key" 2147 | }, 2148 | { 2149 | "name": "numkeys", 2150 | "type": "integer" 2151 | }, 2152 | { 2153 | "name": "key", 2154 | "type": "key", 2155 | "multiple": true 2156 | }, 2157 | { 2158 | "command": "WEIGHTS", 2159 | "name": "weight", 2160 | "type": "integer", 2161 | "variadic": true, 2162 | "optional": true 2163 | }, 2164 | { 2165 | "command": "AGGREGATE", 2166 | "name": "aggregate", 2167 | "type": "enum", 2168 | "enum": ["SUM", "MIN", "MAX"], 2169 | "optional": true 2170 | } 2171 | ], 2172 | "since": "2.0.0", 2173 | "group": "sorted_set" 2174 | } 2175 | } 2176 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | python-toredis (0.0.7) unstable; urgency=low 2 | 3 | * New build. 4 | 5 | -- Serge S. Koval Wed, 26 Dec 2012 14:16:39 +0200 6 | 7 | python-toredis (0.0.6) unstable; urgency=low 8 | 9 | * New build. 10 | 11 | -- Serge S. Koval Wed, 26 Dec 2012 14:11:55 +0200 12 | 13 | python-toredis (0.0.5) unstable; urgency=low 14 | 15 | * New build. 16 | 17 | -- Serge S. Koval Wed, 26 Dec 2012 14:10:57 +0200 18 | 19 | python-toredis (0.0.4) unstable; urgency=low 20 | 21 | * New build. 22 | 23 | -- Serge S. Koval Tue, 18 Dec 2012 16:47:49 +0200 24 | 25 | python-toredis (0.0.3) unstable; urgency=low 26 | 27 | * New build. 28 | 29 | -- Serge S. Koval Tue, 18 Dec 2012 15:59:37 +0200 30 | 31 | python-toredis (0.0.2) unstable; urgency=low 32 | 33 | * New build. 34 | 35 | -- Serge S. Koval Tue, 30 Oct 2012 13:28:53 +0200 36 | 37 | python-toredis (0.0.1) unstable; urgency=low 38 | 39 | * Initial version 40 | 41 | -- Serge S. Koval Sat, 15 October 2012 13:13:14 +0100 42 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: python-toredis 2 | Section: python 3 | Priority: optional 4 | Maintainer: Serge S. Koval 5 | Build-Depends: debhelper (>= 7), python (>= 2.7) 6 | Standards-Version: 3.9.2 7 | Homepage: http://github.com/mrjoes/toredis 8 | 9 | Package: python-toredis 10 | Architecture: all 11 | Depends: ${misc:Depends}, ${python:Depends} 12 | Description: Persistent key-value database with network interface (Python library) 13 | Redis is a key-value database in a similar vein to memcache but the dataset 14 | is non-volatile. Redis additionally provides native support for atomically 15 | manipulating and querying data structures such as lists and sets. 16 | . 17 | The dataset is stored entirely in memory and periodically flushed to disk. 18 | . 19 | This package contains Python bindings to Tornado and Redis. 20 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Authors: 2 | Josh Marshall 3 | Andrew Grigorev 4 | Serge S. Koval 5 | Download: http://github.com/mrjoes/toredis/ 6 | 7 | Files: * 8 | License: APACHE 9 | Licensed under the Apache License, Version 2.0 (the "License"); 10 | you may not use this file except in compliance with the License. 11 | You may obtain a copy of the License at 12 | 13 | http://www.apache.org/licenses/LICENSE-2.0 14 | 15 | Unless required by applicable law or agreed to in writing, software 16 | distributed under the License is distributed on an "AS IS" BASIS, 17 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | See the License for the specific language governing permissions and 19 | limitations under the License. 20 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh --with python2 $@ 5 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /examples/auth.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import tornado.ioloop 3 | from toredis import Client 4 | 5 | 6 | def test_auth(): 7 | # Assuming that 12345 is your redis pasword 8 | redis.auth('12345', after_auth) 9 | 10 | 11 | def after_auth(status): 12 | print('Authentication status:', status) 13 | assert status == 'OK' 14 | io_loop.stop() 15 | 16 | 17 | if __name__ == "__main__": 18 | logging.basicConfig() 19 | 20 | io_loop = tornado.ioloop.IOLoop.instance() 21 | 22 | redis = Client() 23 | redis.connect('localhost', callback=test_auth) 24 | io_loop.start() 25 | -------------------------------------------------------------------------------- /examples/genauth.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import tornado.ioloop 3 | from tornado import gen 4 | from toredis import Client 5 | 6 | 7 | @gen.engine 8 | def test(): 9 | # Authenticate first 10 | status = yield gen.Task(redis.auth, '12345') 11 | assert status == 'OK' 12 | 13 | # Select database 14 | status = yield gen.Task(redis.select, '0') 15 | assert status == 'OK' 16 | 17 | print('Success') 18 | 19 | io_loop.stop() 20 | 21 | if __name__ == "__main__": 22 | logging.basicConfig() 23 | 24 | io_loop = tornado.ioloop.IOLoop.instance() 25 | 26 | redis = Client() 27 | redis.connect('localhost', callback=test) 28 | io_loop.start() 29 | -------------------------------------------------------------------------------- /examples/reconnect.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import tornado.ioloop 3 | from tornado import gen 4 | from toredis import Client 5 | 6 | REDIS_USER_STATUS_DB = 1 7 | REDIS_CONFIG = { 8 | "server": "localhost", 9 | "port": 6379, 10 | "password":"", 11 | "db": REDIS_USER_STATUS_DB, 12 | } 13 | 14 | 15 | def test_set(): 16 | """ 17 | Run SET command and verify status 18 | """ 19 | def after_set(status): 20 | print 'status=', status 21 | assert status == 'OK' 22 | test_get() 23 | 24 | # Connected to redis, set some key 25 | redis.set('test_key', 'foobar', callback=after_set) 26 | 27 | 28 | def test_get(): 29 | """ 30 | Run GET command and verify previously set value 31 | """ 32 | def after_get(status): 33 | assert status == 'foobar' 34 | test_gen() 35 | 36 | redis.get('test_key', callback=after_get) 37 | 38 | 39 | @gen.engine 40 | def test_gen(): 41 | """ 42 | ``gen.engine`` example 43 | """ 44 | # Set test_key2 with 10 45 | result = yield gen.Task(redis.set, 'test_key2', 10) 46 | assert result == 'OK' 47 | 48 | # Increment test_key2. 49 | result = yield gen.Task(redis.incr, 'test_key2') 50 | print 'result=', result 51 | assert result == 11 52 | 53 | # Get test_key2 value. Redis will return string instead of number. 54 | result = yield gen.Task(redis.get, 'test_key2') 55 | print 'result =', result 56 | assert result == '11' 57 | 58 | 59 | class ToRedisClient(Client): 60 | 61 | def on_disconnect(self): 62 | print 'on_disconnect' 63 | if self.auto_reconnect: 64 | self._io_loop.call_later(1, self.reconnect) 65 | 66 | def connect(self, 67 | host = 'localhost', 68 | port = 6379, 69 | password = '', 70 | database = 0, 71 | auto_reconnect = True): 72 | self.host = host 73 | self.port = port 74 | self.password = password 75 | self.database = database 76 | self.auto_reconnect = auto_reconnect 77 | self.reconnect() 78 | 79 | def reconnect(self): 80 | try: 81 | super(ToRedisClient, self).connect(self.host, self.port, self.auth_first) 82 | except Exception as ex: 83 | print 'reconnect ex=', ex 84 | 85 | print 'is_connected=', self.is_connected() 86 | 87 | @gen.engine 88 | def auth_first(self): 89 | # Authenticate first 90 | status = yield gen.Task(self.auth, self.password) 91 | print 'status=', status 92 | 93 | # Select database 94 | status = yield gen.Task(self.select, self.database) 95 | assert status == 'OK' 96 | 97 | print 'auth_first Success' 98 | 99 | test_set() 100 | 101 | if __name__ == "__main__": 102 | logging.basicConfig() 103 | 104 | redis = ToRedisClient() 105 | redis.connect(host=REDIS_CONFIG['server'], 106 | port=REDIS_CONFIG['port'], 107 | password=REDIS_CONFIG['password'], 108 | database=REDIS_CONFIG['db'] 109 | ) 110 | 111 | tornado.ioloop.IOLoop.instance().start() 112 | -------------------------------------------------------------------------------- /examples/simple.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import tornado.ioloop 3 | from tornado import gen 4 | from toredis import Client 5 | 6 | 7 | def finish(): 8 | """ 9 | Stop tornado loop and exit from application 10 | """ 11 | tornado.ioloop.IOLoop.instance().stop() 12 | 13 | 14 | def test_set(): 15 | """ 16 | Run SET command and verify status 17 | """ 18 | def after_set(status): 19 | assert status == 'OK' 20 | test_get() 21 | 22 | # Connected to redis, set some key 23 | redis.set('test_key', 'foobar', callback=after_set) 24 | 25 | 26 | def test_get(): 27 | """ 28 | Run GET command and verify previously set value 29 | """ 30 | def after_get(status): 31 | assert status == 'foobar' 32 | test_gen() 33 | 34 | redis.get('test_key', callback=after_get) 35 | 36 | 37 | @gen.engine 38 | def test_gen(): 39 | """ 40 | ``gen.engine`` example 41 | """ 42 | # Set test_key2 with 10 43 | result = yield gen.Task(redis.set, 'test_key2', 10) 44 | assert result == 'OK' 45 | 46 | # Increment test_key2. 47 | result = yield gen.Task(redis.incr, 'test_key2') 48 | assert result == 11 49 | 50 | # Get test_key2 value. Redis will return string instead of number. 51 | result = yield gen.Task(redis.get, 'test_key2') 52 | assert result == '11' 53 | 54 | finish() 55 | 56 | 57 | if __name__ == "__main__": 58 | logging.basicConfig() 59 | 60 | redis = Client() 61 | redis.connect('localhost', callback=test_set) 62 | tornado.ioloop.IOLoop.instance().start() 63 | -------------------------------------------------------------------------------- /gen_commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | import json 5 | import os 6 | 7 | from textwrap import TextWrapper 8 | 9 | wrapper = TextWrapper(subsequent_indent=' ') 10 | wrap = wrapper.wrap 11 | 12 | 13 | def get_commands(): 14 | return json.load( 15 | open(os.path.join(os.path.dirname(__file__), 'commands.json')) 16 | ) 17 | 18 | 19 | def argname(name): 20 | return name.lower().replace('-', '_').replace(':', '_') 21 | 22 | def parse_command(command): 23 | return '"%s"' % command.replace(' ', '", "') 24 | 25 | def parse_arguments(command, arguments): 26 | args = ['self'] 27 | doc = [] 28 | code = ['{args} = [%s]' % parse_command(command)] 29 | 30 | for arg in arguments: 31 | # Sub-command parsing 32 | if 'command' in arg: 33 | cmd = argname(arg['command']) 34 | 35 | if cmd in args: 36 | raise Exception('Command %s is already in args!' % cmd) 37 | 38 | cmd_default = 'None' 39 | 40 | if arg.get('multiple'): 41 | cmd_default = 'tuple()' 42 | 43 | if isinstance(arg['name'], list): 44 | code.append('for %s in %s:' % ( 45 | ', '.join([argname(i) for i in arg['name']]), 46 | cmd 47 | )) 48 | 49 | code.append( 50 | ' {args}.append("%s")' % arg['command'] 51 | ) 52 | 53 | for i in arg['name']: 54 | code.append(' {args}.append(%s)' % argname(i)) 55 | else: 56 | code.append('for %s in %s:' % (argname(arg['name']), cmd)) 57 | code.append(' {args}.append("%s")' % arg['command']) 58 | code.append(' {args}.append(%s)' % argname(arg['name'])) 59 | elif arg.get('variadic'): 60 | cmd_default = 'tuple()' 61 | code.append('if len(%s):' % cmd) 62 | code.append(' {args}.append("%s")' % arg['command']) 63 | if isinstance(arg['name'], list): 64 | code.append(' for %s in %s:' % ( 65 | ', '.join([argname(i) for i in arg['name']]), 66 | cmd 67 | )) 68 | for i in arg['name']: 69 | code.append(' {args}.append(%s)' % argname(i)) 70 | else: 71 | code.append(' {args}.extend(%s)' % cmd) 72 | else: 73 | if arg.get('optional'): 74 | prefix = ' ' 75 | code.append('if %s:' % cmd) 76 | else: 77 | prefix = '' 78 | 79 | code.append(prefix + '{args}.append("%s")' % arg['command']) 80 | 81 | if isinstance(arg['name'], list): 82 | code.append(prefix + '%s = %s' % ( 83 | ', '.join([argname(i) for i in arg['name']]), 84 | cmd 85 | )) 86 | for i in arg['name']: 87 | code.append(prefix + '{args}.append(%s)' % argname(i)) 88 | else: 89 | code.append(prefix + '{args}.append(%s)' % cmd) 90 | 91 | if 'optional' in arg: 92 | args.append('%s=%s' % (cmd, cmd_default)) 93 | else: 94 | args.append(cmd) 95 | 96 | doc.append(':param %s:' % cmd) 97 | # Special case for numkeys parameter 98 | elif arg['name'] == 'numkeys': 99 | # do not adding arg for numkeys argument 100 | assert arguments[arguments.index(arg) + 1] == { 101 | "name": "key", 102 | "type": "key", 103 | "multiple": True 104 | } 105 | code.append('{args}.append(len(keys))') 106 | # If name is list 107 | elif isinstance(arg['name'], list): 108 | # makes no sense for single pairs 109 | assert arg.get('multiple') 110 | 111 | # special case for score and member 112 | if arg['name'] == [u"score", u"member"]: 113 | args.append('member_score_dict') 114 | code.append('for member, score in member_score_dict.items():') 115 | code.append(' {args}.append(score)') 116 | code.append(' {args}.append(member)') 117 | 118 | doc.append(':param member_score_dict:') 119 | doc.append(' member score dictionary') 120 | # value pairs 121 | elif len(arg['name']) == 2 and arg['name'][1] == 'value': 122 | arg_name = argname(arg['name'][0]) 123 | name = '%s_dict' % arg_name 124 | args.append(name) 125 | code.append('for %s, value in %s.items():' % (arg_name, name)) 126 | code.append(' {args}.append(%s)' % arg_name) 127 | code.append(' {args}.append(value)') 128 | 129 | doc.append(':param member_score_dict:') 130 | doc.append(' key value dictionary') 131 | # special case for length = 1 132 | elif len(arg['name']) == 1: 133 | name = '%ss' % argname(arg['name'][0]) 134 | args.append(name) 135 | code.append('if isinstance(%s, string_types):' % name) 136 | code.append(' {args}.append(%s)' % name) 137 | code.append('else:') 138 | code.append(' {args}.extend(%s)' % name) 139 | 140 | doc.append(':param member_score_dict:') 141 | doc.append(' string or list of strings') 142 | else: 143 | raise Exception('Unknown list name group in argument ' 144 | 'specification: %s' % arg['name']) 145 | elif argname(arg['name']) in args: 146 | raise Exception( 147 | 'Argument %s is already in args!' % argname(arg['name']) 148 | ) 149 | elif 'multiple' in arg: 150 | # If it is last parameter, use *arg notation 151 | name = '%ss' % argname(arg['name']) 152 | if arg.get('optional'): 153 | args.append('%s=[]' % name) 154 | else: 155 | args.append(name) 156 | code.append('if isinstance(%s, string_types):' % name) 157 | code.append(' {args}.append(%s)' % name) 158 | code.append('else:') 159 | code.append(' {args}.extend(%s)' % name) 160 | 161 | doc.append(':param %s:' % name) 162 | doc.append(' string or list of strings') 163 | elif 'enum' in arg and len(arg['enum']) == 1 and arg.get('optional'): 164 | name = argname(arg['name']) 165 | args.append('%s=False' % name) 166 | code.append('if %s:' % name) 167 | code.append(' {args}.append("%s")' % arg['enum'][0]) 168 | 169 | doc.append(':param %s:' % name) 170 | else: 171 | name = argname(arg['name']) 172 | code.append('{args}.append(%s)' % name) 173 | if arg.get('optional') == True: 174 | args.append('%s=None' % name) 175 | else: 176 | args.append(name) 177 | doc.append(':param %s:' % name) 178 | 179 | args.append('callback=None') 180 | if len(code) > 1: 181 | code.append('self.send_message({args}, callback)') 182 | else: 183 | code = ['self.send_message([%s], callback)' % parse_command(command)] 184 | 185 | if 'args' in args or any(arg.startswith('args=') for arg in args): 186 | code = [line.format(args='message_args') for line in code] 187 | else: 188 | code = [line.format(args='args') for line in code] 189 | 190 | return args, doc, code 191 | 192 | 193 | name_map = { 194 | 'del': 'delete', 195 | 'exec': 'execute' 196 | } 197 | 198 | 199 | def get_command_name(command): 200 | name = command.lower().replace(' ', '_') 201 | if name in name_map: 202 | return name_map[name] 203 | else: 204 | return name 205 | 206 | 207 | def get_command_code(func_name, cmd, params): 208 | 209 | args, args_doc, args_code = parse_arguments( 210 | cmd, params.get('arguments', []) 211 | ) 212 | 213 | args_doc = [" %s" % i for i in args_doc] 214 | args_code = [" %s" % i for i in args_code] 215 | 216 | wrap = TextWrapper().wrap 217 | 218 | lines = [] 219 | lines.append('def %s(%s):' % (func_name, ', '.join(args))) 220 | lines.append(' """') 221 | doc = [] 222 | if 'summary' in params: 223 | doc.extend(wrap(params['summary'])) 224 | if args_doc: 225 | doc.append('') 226 | doc.extend(args_doc) 227 | if 'complexity' in params: 228 | doc.append('') 229 | doc.append('Complexity') 230 | doc.append('----------') 231 | doc.extend(wrap(params['complexity'])) 232 | lines.extend([' %s' % line if line else line for line in doc]) 233 | lines.append(' """') 234 | lines.extend(args_code) 235 | 236 | return lines 237 | 238 | 239 | def get_class_source(class_name): 240 | lines = ['class %s(object):' % class_name, ''] 241 | for cmd, params in sorted(get_commands().items()): 242 | for line in get_command_code(get_command_name(cmd), cmd, params): 243 | lines.append(' %s' % line if line else line) 244 | lines.append('') 245 | return '\n'.join(lines) 246 | 247 | 248 | def get_imports(): 249 | imports = 'from toredis._compat import string_types' 250 | return imports + '\n' * 3 251 | 252 | 253 | def compile_commands(): 254 | ret = {} 255 | for cmd, params in sorted(get_commands().items()): 256 | name = get_command_name(cmd) 257 | lines = get_command_code(name, cmd, params) 258 | code = compile('\n'.join(lines), "", "exec") 259 | ctx = {} 260 | exec(code in ctx) 261 | ret[name] = ctx[name] 262 | return ret 263 | 264 | 265 | if __name__ == "__main__": 266 | with open(os.path.join(os.path.dirname(__file__), 'toredis/commands.py'), 'w') as f: 267 | f.write(get_imports()) 268 | f.write(get_class_source('RedisCommandsMixin')) 269 | print('Generated commands.py') 270 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | """ 15 | 16 | try: 17 | from setuptools import setup, find_packages 18 | except ImportError: 19 | from distribute_setup import use_setuptools 20 | use_setuptools() 21 | from setuptools import setup, find_packages 22 | 23 | setup( 24 | name='toredis', 25 | version='0.1.2', 26 | description='Really simple async Redis client for Tornado', 27 | author='Josh Marshall', 28 | author_email='catchjosh@gmail.com', 29 | url="http://github.com/mrjoes/toredis/", 30 | license="http://www.apache.org/licenses/LICENSE-2.0", 31 | packages=['toredis'], 32 | test_suite='tests.all_tests', 33 | install_requires=['tornado', 'hiredis'], 34 | ) 35 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from tests.test_client import TestClient 4 | from tests.test_handler import TestRedis 5 | from tests.test_pipeline import TestPipeline 6 | 7 | TEST_MODULES = [ 8 | "test_client", 9 | "test_handler", 10 | "test_pipeline", 11 | ] 12 | 13 | def all_tests(): 14 | suite = unittest.TestSuite() 15 | suite.addTest(unittest.makeSuite(TestClient)) 16 | suite.addTest(unittest.makeSuite(TestRedis)) 17 | suite.addTest(unittest.makeSuite(TestPipeline)) 18 | return suite 19 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | from tornado.testing import AsyncTestCase 2 | import time 3 | from toredis.client import Client 4 | from tornado import gen 5 | 6 | class TestClient(AsyncTestCase): 7 | """ Test the client """ 8 | 9 | def test_connect(self): 10 | client = Client(io_loop=self.io_loop) 11 | result = {} 12 | 13 | def callback(): 14 | result["connected"] = True 15 | self.stop() 16 | client.connect(callback=callback) 17 | self.wait() 18 | # blocks 19 | self.assertTrue("connected" in result) 20 | 21 | @gen.engine 22 | def test_set_command(self): 23 | client = Client(io_loop=self.io_loop) 24 | result = {} 25 | 26 | def set_callback(response): 27 | result["set"] = response 28 | self.stop() 29 | 30 | client.connect() 31 | client.set("foo", "bar", callback=set_callback) 32 | self.wait() 33 | #blocks 34 | self.assertTrue("set" in result) 35 | self.assertEqual(result["set"], b"OK") 36 | 37 | conn = Client() 38 | conn.connect() 39 | value = yield gen.Task(conn.get, "foo") 40 | self.assertEqual("bar", value) 41 | 42 | @gen.engine 43 | def test_get_command(self): 44 | client = Client(io_loop=self.io_loop) 45 | result = None 46 | 47 | def get_callback(response): 48 | result = response 49 | self.stop() 50 | time_string = "%s" % time.time() 51 | conn = Client() 52 | conn.connect() 53 | yield gen.Task(conn.set, "foo", time_string) 54 | 55 | client.connect() 56 | client.get("foo", callback=get_callback) 57 | self.wait() 58 | #blocks 59 | 60 | self.assertTrue(result is not None, 'result is %s' % result) 61 | self.assertEqual(time_string, result) 62 | 63 | @gen.engine 64 | def test_sub_command(self): 65 | client = Client(io_loop=self.io_loop) 66 | result = {"message_count": 0} 67 | conn = Client() 68 | conn.connect() 69 | 70 | client.connect() 71 | response = yield gen.Task(client.subscribe, "foobar") 72 | if response[0] == "subscribe": 73 | result["sub"] = response 74 | yield gen.Task(conn.publish, "foobar", "new message!") 75 | elif response[0] == "message": 76 | result["message_count"] += 1 77 | if result["message_count"] < 100: 78 | count = result["message_count"] 79 | value = yield gen.Task(conn.publish, 80 | "foobar", "new message %s!" % count) 81 | result["message"] = response[2] 82 | 83 | self.assertTrue("sub" in result) 84 | self.assertTrue("message" in result) 85 | self.assertTrue(result["message"], "new message 99!") 86 | 87 | def test_pub_command(self): 88 | client = Client(io_loop=self.io_loop) 89 | result = {} 90 | 91 | def pub_callback(response): 92 | result["pub"] = response 93 | self.stop() 94 | client.connect() 95 | client.publish("foobar", "message", callback=pub_callback) 96 | self.wait() 97 | # blocks 98 | self.assertTrue("pub" in result) 99 | self.assertEqual(result["pub"], 0) # no subscribers yet 100 | 101 | def test_blpop(self): 102 | client = Client(io_loop=self.io_loop) 103 | result = {} 104 | 105 | def rpush_callback(response): 106 | result["push"] = response 107 | 108 | def blpop_callback(response): 109 | result["pop"] = response 110 | self.stop() 111 | 112 | client.blpop("test", 0, blpop_callback) 113 | 114 | client.connect() 115 | client.rpush("test", "dummy", rpush_callback) 116 | self.wait() 117 | self.assertEqual(result["pop"], [b"test", b"dummy"]) 118 | 119 | def test_disconnect(self): 120 | client = Client(io_loop=self.io_loop) 121 | client.connect() 122 | client.close() 123 | with self.assertRaises(IOError): 124 | client._stream.read_bytes(1024, lambda x: x) 125 | 126 | def test_pubsub_disconnect(self): 127 | client = Client(io_loop=self.io_loop) 128 | client.connect() 129 | client.subscribe("foo", lambda: None) 130 | client.close() 131 | with self.assertRaises(IOError): 132 | client._stream.read_bytes(1024, lambda x: x) 133 | -------------------------------------------------------------------------------- /tests/test_handler.py: -------------------------------------------------------------------------------- 1 | from tornado.testing import AsyncHTTPTestCase 2 | from tornado.web import RequestHandler, Application, asynchronous 3 | from tornado.ioloop import IOLoop 4 | 5 | from toredis import Client 6 | import time 7 | 8 | 9 | class Handler(RequestHandler): 10 | 11 | @asynchronous 12 | def get(self): 13 | self.client = Client() 14 | self.client.connect() 15 | self.client.subscribe("foo", callback=self.on_receive) 16 | 17 | def on_receive(self, msg): 18 | """ New message, close out connection. """ 19 | msg_type, msg_channel, msg = msg 20 | if msg_type == b"message": 21 | self.finish({"message": msg.decode()}) 22 | 23 | 24 | class TestRedis(AsyncHTTPTestCase): 25 | 26 | def get_app(self): 27 | return Application([(r"/", Handler)]) 28 | 29 | def get_new_ioloop(self): 30 | return IOLoop.instance() # the default Client loop 31 | 32 | def test_subscribe(self): 33 | """ Tests a subscription message """ 34 | 35 | # conn = redis.Redis() 36 | conn = Client() 37 | conn.connect() 38 | 39 | def publish_message(): 40 | conn.publish("foo", "bar") 41 | 42 | self.io_loop.add_timeout(time.time() + 0.5, publish_message) 43 | response = self.fetch("/") 44 | # blocks 45 | self.assertEqual(response.code, 200) 46 | -------------------------------------------------------------------------------- /tests/test_pipeline.py: -------------------------------------------------------------------------------- 1 | from tornado.testing import AsyncTestCase 2 | 3 | from toredis.client import Client 4 | from toredis.pipeline import Pipeline 5 | 6 | from hiredis import ReplyError 7 | 8 | 9 | class TestPipeline(AsyncTestCase): 10 | 11 | def setUp(self): 12 | super(TestPipeline, self).setUp() 13 | self.client = Client(io_loop=self.io_loop) 14 | self.client.connect(callback=self.stop) 15 | self.wait() 16 | self.client.flushdb(callback=self.stop) 17 | self.wait() 18 | 19 | def tearDown(self): 20 | self.client.close() 21 | super(TestPipeline, self).tearDown() 22 | 23 | def test_pipeline_send(self): 24 | pipeline = self.client.pipeline() 25 | pipeline.set('foo1', 'bar1') 26 | pipeline.set('foo2', 'bar2') 27 | pipeline.set('foo3', 'bar3') 28 | pipeline.get('foo1') 29 | pipeline.mget(['foo2', 'foo3']) 30 | pipeline.send(callback=self.stop) 31 | response = self.wait() 32 | self.assertEqual( 33 | response, [b'OK', b'OK', b'OK', b'bar1', [b'bar2', b'bar3']] 34 | ) 35 | 36 | def test_pipeline_reset(self): 37 | pipeline = self.client.pipeline() 38 | pipeline.set('foo1', 'bar1') 39 | pipeline.get('foo1') 40 | pipeline.reset() 41 | pipeline.set('foo2', 'bar2') 42 | pipeline.get('foo2') 43 | pipeline.send(callback=self.stop) 44 | response = self.wait() 45 | self.assertEqual(response, [b'OK', b'bar2']) 46 | 47 | def test_pipeline_with_error(self): 48 | pipeline = self.client.pipeline() 49 | pipeline.eval('invalid', [], []) 50 | pipeline.eval( 51 | 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 52 | ['key1', 'key2'], ['arg1', 'arg2'] 53 | ) 54 | pipeline.send(callback=self.stop) 55 | response = self.wait() 56 | self.assertIsInstance(response[0], ReplyError) 57 | self.assertEqual(response[1], [b'key1', b'key2', b'arg1', b'arg2']) 58 | 59 | def test_pipeline_small(self): 60 | pipeline = self.client.pipeline() 61 | pipeline.set('foo', 'bar') 62 | pipeline.send(callback=self.stop) 63 | response = self.wait() 64 | self.assertEqual(response, [b'OK']) 65 | 66 | def test_pipeline_big(self): 67 | pipeline = self.client.pipeline() 68 | expected = [] 69 | for i in range(10000): 70 | pipeline.set('foo%d' % i, 'bar%d' % i) 71 | pipeline.get('foo%d' % i) 72 | expected.extend([b'OK', b'bar' + str(i).encode()]) 73 | pipeline.send(callback=self.stop) 74 | response = self.wait() 75 | self.assertEqual(response, expected) 76 | 77 | def test_pipeline_and_commands(self): 78 | pipeline = self.client.pipeline() 79 | pipeline.set('foo1', 'bar1') 80 | pipeline.get('foo1') 81 | pipeline.send(callback=self.stop) 82 | response = self.wait() 83 | self.assertEqual(response, [b'OK', b'bar1']) 84 | 85 | self.client.eval( 86 | 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 87 | ['key1', 'key2'], ['arg1', 'arg2'], 88 | callback=self.stop 89 | ) 90 | response = self.wait() 91 | self.assertEqual(response, [b'key1', b'key2', b'arg1', b'arg2']) 92 | 93 | pipeline.set('foo2', 'bar2') 94 | pipeline.get('foo2') 95 | pipeline.send(callback=self.stop) 96 | response = self.wait() 97 | self.assertEqual(response, [b'OK', b'bar2']) 98 | 99 | def test_parallel_pipeline_and_commands(self): 100 | result = {} 101 | 102 | def callback_pipeline1(response): 103 | result['pipeline1'] = response 104 | self.stop() 105 | 106 | def callback_pipeline2(response): 107 | result['pipeline2'] = response 108 | self.stop() 109 | 110 | def callback_eval(response): 111 | result['eval'] = response 112 | self.stop() 113 | 114 | pipeline = self.client.pipeline() 115 | pipeline.set('foo1', 'bar1') 116 | pipeline.get('foo1') 117 | pipeline.send(callback=callback_pipeline1) 118 | 119 | self.client.eval( 120 | 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 121 | ['key1', 'key2'], ['arg1', 'arg2'], 122 | callback=callback_eval 123 | ) 124 | 125 | pipeline.set('foo2', 'bar2') 126 | pipeline.get('foo2') 127 | pipeline.send(callback=callback_pipeline2) 128 | 129 | self.wait(lambda: len(result) == 3) 130 | self.assertEqual(result, { 131 | 'pipeline1': [b'OK', b'bar1'], 132 | 'eval': [b'key1', b'key2', b'arg1', b'arg2'], 133 | 'pipeline2': [b'OK', b'bar2'] 134 | }) 135 | 136 | def test_parallel_pipelines(self): 137 | result = {} 138 | 139 | def callback_pipeline1(response): 140 | result['pipeline1'] = response 141 | self.stop() 142 | 143 | def callback_pipeline2(response): 144 | result['pipeline2'] = response 145 | self.stop() 146 | 147 | pipeline1 = Pipeline(self.client) 148 | pipeline1.set('foo1', 'bar1') 149 | pipeline1.get('foo1') 150 | pipeline1.send(callback=callback_pipeline1) 151 | 152 | pipeline2 = Pipeline(self.client) 153 | pipeline2.set('foo2', 'bar2') 154 | pipeline2.get('foo2') 155 | pipeline2.send(callback=callback_pipeline2) 156 | 157 | self.wait(lambda: len(result) == 2) 158 | self.assertEqual(result, { 159 | 'pipeline1': [b'OK', b'bar1'], 160 | 'pipeline2': [b'OK', b'bar2'] 161 | }) 162 | -------------------------------------------------------------------------------- /toredis/__init__.py: -------------------------------------------------------------------------------- 1 | from toredis.client import Client 2 | from toredis.pipeline import Pipeline 3 | -------------------------------------------------------------------------------- /toredis/_compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | PY2 = sys.version_info[0] == 2 4 | 5 | if not PY2: 6 | text_type = str 7 | string_types = (str,) 8 | integer_types = (int, ) 9 | else: 10 | text_type = unicode 11 | string_types = (str, unicode) 12 | integer_types = (int, long) 13 | -------------------------------------------------------------------------------- /toredis/client.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import socket 3 | 4 | from collections import deque 5 | 6 | import hiredis 7 | 8 | from tornado.iostream import IOStream 9 | from tornado.ioloop import IOLoop 10 | from tornado import stack_context 11 | 12 | from toredis.commands import RedisCommandsMixin 13 | from toredis.pipeline import Pipeline 14 | from toredis._compat import string_types, text_type 15 | 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | class Client(RedisCommandsMixin): 21 | """ 22 | Redis client class 23 | """ 24 | def __init__(self, io_loop=None): 25 | """ 26 | Constructor 27 | 28 | :param io_loop: 29 | Optional IOLoop instance 30 | """ 31 | self._io_loop = io_loop or IOLoop.instance() 32 | 33 | self._stream = None 34 | 35 | self.reader = None 36 | self.callbacks = deque() 37 | 38 | self._sub_callback = False 39 | 40 | def connect(self, host='localhost', port=6379, callback=None): 41 | """ 42 | Connect to redis server 43 | 44 | :param host: 45 | Host to connect to 46 | :param port: 47 | Port 48 | :param callback: 49 | Optional callback to be triggered upon connection 50 | """ 51 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 52 | return self._connect(sock, (host, port), callback) 53 | 54 | def connect_usocket(self, usock, callback=None): 55 | """ 56 | Connect to redis server with unix socket 57 | """ 58 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0) 59 | return self._connect(sock, usock, callback) 60 | 61 | def on_disconnect(self): 62 | """ 63 | Override this method if you want to handle disconnections 64 | """ 65 | pass 66 | 67 | # State 68 | def is_idle(self): 69 | """ 70 | Check if client is not waiting for any responses 71 | """ 72 | return len(self.callbacks) == 0 73 | 74 | def is_connected(self): 75 | """ 76 | Check if client is still connected 77 | """ 78 | return bool(self._stream) and not self._stream.closed() 79 | 80 | def send_message(self, args, callback=None): 81 | """ 82 | Send command to redis 83 | 84 | :param args: 85 | Arguments to send 86 | :param callback: 87 | Callback 88 | """ 89 | # Special case for pub-sub 90 | cmd = args[0] 91 | 92 | if (self._sub_callback is not None and 93 | cmd not in ('PSUBSCRIBE', 'SUBSCRIBE', 'PUNSUBSCRIBE', 'UNSUBSCRIBE', 'QUIT')): 94 | raise ValueError('Cannot run normal command over PUBSUB connection') 95 | 96 | # Send command 97 | self._stream.write(self.format_message(args)) 98 | if callback is not None: 99 | callback = stack_context.wrap(callback) 100 | self.callbacks.append((callback, None)) 101 | 102 | def send_messages(self, args_pipeline, callback=None): 103 | """ 104 | Send command pipeline to redis 105 | 106 | :param args_pipeline: 107 | Arguments pipeline to send 108 | :param callback: 109 | Callback 110 | """ 111 | if not args_pipeline: 112 | # Exit immediately if there's no pipeline commands 113 | # Otherwise registering callback white sending empty message 114 | # will cause callback mismatch going forward 115 | return 116 | 117 | if self._sub_callback is not None: 118 | raise ValueError('Cannot run pipeline over PUBSUB connection') 119 | 120 | # Send command pipeline 121 | messages = [self.format_message(args) for args in args_pipeline] 122 | self._stream.write(b"".join(messages)) 123 | if callback is not None: 124 | callback = stack_context.wrap(callback) 125 | self.callbacks.append((callback, (len(messages), []))) 126 | 127 | def format_message(self, args): 128 | """ 129 | Create redis message 130 | 131 | :param args: 132 | Message data 133 | """ 134 | l = "*%d" % len(args) 135 | lines = [l.encode('utf-8')] 136 | for arg in args: 137 | if not isinstance(arg, string_types): 138 | arg = str(arg) 139 | if isinstance(arg, text_type): 140 | arg = arg.encode('utf-8') 141 | l = "$%d" % len(arg) 142 | lines.append(l.encode('utf-8')) 143 | lines.append(arg) 144 | lines.append(b"") 145 | return b"\r\n".join(lines) 146 | 147 | def close(self): 148 | """ 149 | Close redis connection 150 | """ 151 | self.quit() 152 | self._stream.close() 153 | 154 | # Pub/sub commands 155 | def psubscribe(self, patterns, callback=None): 156 | """ 157 | Customized psubscribe command - will keep one callback for all incoming messages 158 | 159 | :param patterns: 160 | string or list of strings 161 | :param callback: 162 | callback 163 | """ 164 | self._set_sub_callback(callback) 165 | super(Client, self).psubscribe(patterns) 166 | 167 | def subscribe(self, channels, callback=None): 168 | """ 169 | Customized subscribe command - will keep one callback for all incoming messages 170 | 171 | :param channels: 172 | string or list of strings 173 | :param callback: 174 | Callback 175 | """ 176 | self._set_sub_callback(callback) 177 | super(Client, self).subscribe(channels) 178 | 179 | def _set_sub_callback(self, callback): 180 | if self._sub_callback is None: 181 | self._sub_callback = callback 182 | 183 | assert self._sub_callback == callback 184 | 185 | # Helpers 186 | def _connect(self, sock, addr, callback): 187 | self._reset() 188 | 189 | self._stream = IOStream(sock, io_loop=self._io_loop) 190 | 191 | def _stream_connect_callback(): 192 | callback() 193 | 194 | self._stream.read_until_close(self._on_close, self._on_read) 195 | 196 | self._stream.connect(addr, callback=_stream_connect_callback) 197 | 198 | # Event handlers 199 | def _on_read(self, data): 200 | self.reader.feed(data) 201 | 202 | resp = self.reader.gets() 203 | 204 | while resp is not False: 205 | if self._sub_callback: 206 | try: 207 | self._sub_callback(resp) 208 | except: 209 | logger.exception('SUB callback failed') 210 | else: 211 | if self.callbacks: 212 | callback, callback_data = self.callbacks[0] 213 | if callback_data is None: 214 | callback_resp = resp 215 | else: 216 | # handle pipeline responses 217 | num_resp, callback_resp = callback_data 218 | callback_resp.append(resp) 219 | while len(callback_resp) < num_resp: 220 | resp = self.reader.gets() 221 | if resp is False: 222 | # callback_resp is yet incomplete 223 | return 224 | callback_resp.append(resp) 225 | self.callbacks.popleft() 226 | if callback is not None: 227 | try: 228 | callback(callback_resp) 229 | except: 230 | logger.exception('Callback failed') 231 | else: 232 | logger.debug('Ignored response: %s' % repr(resp)) 233 | 234 | resp = self.reader.gets() 235 | 236 | def _on_close(self, data=None): 237 | if data is not None: 238 | self._on_read(data) 239 | 240 | # Trigger any pending callbacks 241 | callbacks = self.callbacks 242 | self.callbacks = deque() 243 | 244 | if callbacks: 245 | for cb in callbacks: 246 | callback, callback_data = cb 247 | if callback is not None: 248 | try: 249 | callback(None) 250 | except: 251 | logger.exception('Exception in callback') 252 | 253 | if self._sub_callback is not None: 254 | try: 255 | self._sub_callback(None) 256 | except: 257 | logger.exception('Exception in SUB callback') 258 | self._sub_callback = None 259 | 260 | # Trigger on_disconnect 261 | self.on_disconnect() 262 | 263 | def _reset(self): 264 | self.reader = hiredis.Reader() 265 | self._sub_callback = None 266 | 267 | def pipeline(self): 268 | return Pipeline(self) 269 | -------------------------------------------------------------------------------- /toredis/commands.py: -------------------------------------------------------------------------------- 1 | from toredis._compat import string_types 2 | 3 | 4 | class RedisCommandsMixin(object): 5 | 6 | def append(self, key, value, callback=None): 7 | """ 8 | Append a value to a key 9 | 10 | :param key: 11 | :param value: 12 | 13 | Complexity 14 | ---------- 15 | O(1). The amortized time complexity is O(1) assuming the appended 16 | value is small and the already present value is of any size, since the 17 | dynamic string library used by Redis will double the free space 18 | available on every reallocation. 19 | """ 20 | args = ["APPEND"] 21 | args.append(key) 22 | args.append(value) 23 | self.send_message(args, callback) 24 | 25 | def auth(self, password, callback=None): 26 | """ 27 | Authenticate to the server 28 | 29 | :param password: 30 | """ 31 | args = ["AUTH"] 32 | args.append(password) 33 | self.send_message(args, callback) 34 | 35 | def bgrewriteaof(self, callback=None): 36 | """ 37 | Asynchronously rewrite the append-only file 38 | """ 39 | self.send_message(["BGREWRITEAOF"], callback) 40 | 41 | def bgsave(self, callback=None): 42 | """ 43 | Asynchronously save the dataset to disk 44 | """ 45 | self.send_message(["BGSAVE"], callback) 46 | 47 | def bitcount(self, key, start=None, end=None, callback=None): 48 | """ 49 | Count set bits in a string 50 | 51 | :param key: 52 | :param start: 53 | :param end: 54 | 55 | Complexity 56 | ---------- 57 | O(N) 58 | """ 59 | args = ["BITCOUNT"] 60 | args.append(key) 61 | args.append(start) 62 | args.append(end) 63 | self.send_message(args, callback) 64 | 65 | def bitop(self, operation, destkey, keys, callback=None): 66 | """ 67 | Perform bitwise operations between strings 68 | 69 | :param operation: 70 | :param destkey: 71 | :param keys: 72 | string or list of strings 73 | 74 | Complexity 75 | ---------- 76 | O(N) 77 | """ 78 | args = ["BITOP"] 79 | args.append(operation) 80 | args.append(destkey) 81 | if isinstance(keys, string_types): 82 | args.append(keys) 83 | else: 84 | args.extend(keys) 85 | self.send_message(args, callback) 86 | 87 | def blpop(self, keys, timeout, callback=None): 88 | """ 89 | Remove and get the first element in a list, or block until one is 90 | available 91 | 92 | :param keys: 93 | string or list of strings 94 | :param timeout: 95 | 96 | Complexity 97 | ---------- 98 | O(1) 99 | """ 100 | args = ["BLPOP"] 101 | if isinstance(keys, string_types): 102 | args.append(keys) 103 | else: 104 | args.extend(keys) 105 | args.append(timeout) 106 | self.send_message(args, callback) 107 | 108 | def brpop(self, keys, timeout, callback=None): 109 | """ 110 | Remove and get the last element in a list, or block until one is 111 | available 112 | 113 | :param keys: 114 | string or list of strings 115 | :param timeout: 116 | 117 | Complexity 118 | ---------- 119 | O(1) 120 | """ 121 | args = ["BRPOP"] 122 | if isinstance(keys, string_types): 123 | args.append(keys) 124 | else: 125 | args.extend(keys) 126 | args.append(timeout) 127 | self.send_message(args, callback) 128 | 129 | def brpoplpush(self, source, destination, timeout, callback=None): 130 | """ 131 | Pop a value from a list, push it to another list and return it; or 132 | block until one is available 133 | 134 | :param source: 135 | :param destination: 136 | :param timeout: 137 | 138 | Complexity 139 | ---------- 140 | O(1) 141 | """ 142 | args = ["BRPOPLPUSH"] 143 | args.append(source) 144 | args.append(destination) 145 | args.append(timeout) 146 | self.send_message(args, callback) 147 | 148 | def client_kill(self, ip_port, callback=None): 149 | """ 150 | Kill the connection of a client 151 | 152 | :param ip_port: 153 | 154 | Complexity 155 | ---------- 156 | O(N) where N is the number of client connections 157 | """ 158 | args = ["CLIENT", "KILL"] 159 | args.append(ip_port) 160 | self.send_message(args, callback) 161 | 162 | def client_list(self, callback=None): 163 | """ 164 | Get the list of client connections 165 | 166 | Complexity 167 | ---------- 168 | O(N) where N is the number of client connections 169 | """ 170 | self.send_message(["CLIENT", "LIST"], callback) 171 | 172 | def config_get(self, parameter, callback=None): 173 | """ 174 | Get the value of a configuration parameter 175 | 176 | :param parameter: 177 | """ 178 | args = ["CONFIG", "GET"] 179 | args.append(parameter) 180 | self.send_message(args, callback) 181 | 182 | def config_resetstat(self, callback=None): 183 | """ 184 | Reset the stats returned by INFO 185 | 186 | Complexity 187 | ---------- 188 | O(1) 189 | """ 190 | self.send_message(["CONFIG", "RESETSTAT"], callback) 191 | 192 | def config_set(self, parameter, value, callback=None): 193 | """ 194 | Set a configuration parameter to the given value 195 | 196 | :param parameter: 197 | :param value: 198 | """ 199 | args = ["CONFIG", "SET"] 200 | args.append(parameter) 201 | args.append(value) 202 | self.send_message(args, callback) 203 | 204 | def dbsize(self, callback=None): 205 | """ 206 | Return the number of keys in the selected database 207 | """ 208 | self.send_message(["DBSIZE"], callback) 209 | 210 | def debug_object(self, key, callback=None): 211 | """ 212 | Get debugging information about a key 213 | 214 | :param key: 215 | """ 216 | args = ["DEBUG", "OBJECT"] 217 | args.append(key) 218 | self.send_message(args, callback) 219 | 220 | def debug_segfault(self, callback=None): 221 | """ 222 | Make the server crash 223 | """ 224 | self.send_message(["DEBUG", "SEGFAULT"], callback) 225 | 226 | def decr(self, key, callback=None): 227 | """ 228 | Decrement the integer value of a key by one 229 | 230 | :param key: 231 | 232 | Complexity 233 | ---------- 234 | O(1) 235 | """ 236 | args = ["DECR"] 237 | args.append(key) 238 | self.send_message(args, callback) 239 | 240 | def decrby(self, key, decrement, callback=None): 241 | """ 242 | Decrement the integer value of a key by the given number 243 | 244 | :param key: 245 | :param decrement: 246 | 247 | Complexity 248 | ---------- 249 | O(1) 250 | """ 251 | args = ["DECRBY"] 252 | args.append(key) 253 | args.append(decrement) 254 | self.send_message(args, callback) 255 | 256 | def delete(self, keys, callback=None): 257 | """ 258 | Delete a key 259 | 260 | :param keys: 261 | string or list of strings 262 | 263 | Complexity 264 | ---------- 265 | O(N) where N is the number of keys that will be removed. When a key to 266 | remove holds a value other than a string, the individual complexity 267 | for this key is O(M) where M is the number of elements in the list, 268 | set, sorted set or hash. Removing a single key that holds a string 269 | value is O(1). 270 | """ 271 | args = ["DEL"] 272 | if isinstance(keys, string_types): 273 | args.append(keys) 274 | else: 275 | args.extend(keys) 276 | self.send_message(args, callback) 277 | 278 | def discard(self, callback=None): 279 | """ 280 | Discard all commands issued after MULTI 281 | """ 282 | self.send_message(["DISCARD"], callback) 283 | 284 | def dump(self, key, callback=None): 285 | """ 286 | Return a serialized version of the value stored at the specified key. 287 | 288 | :param key: 289 | 290 | Complexity 291 | ---------- 292 | O(1) to access the key and additional O(N*M) to serialized it, where N 293 | is the number of Redis objects composing the value and M their average 294 | size. For small string values the time complexity is thus O(1)+O(1*M) 295 | where M is small, so simply O(1). 296 | """ 297 | args = ["DUMP"] 298 | args.append(key) 299 | self.send_message(args, callback) 300 | 301 | def echo(self, message, callback=None): 302 | """ 303 | Echo the given string 304 | 305 | :param message: 306 | """ 307 | args = ["ECHO"] 308 | args.append(message) 309 | self.send_message(args, callback) 310 | 311 | def eval(self, script, keys, args, callback=None): 312 | """ 313 | Execute a Lua script server side 314 | 315 | :param script: 316 | :param keys: 317 | string or list of strings 318 | :param args: 319 | string or list of strings 320 | 321 | Complexity 322 | ---------- 323 | Depends on the script that is executed. 324 | """ 325 | message_args = ["EVAL"] 326 | message_args.append(script) 327 | message_args.append(len(keys)) 328 | if isinstance(keys, string_types): 329 | message_args.append(keys) 330 | else: 331 | message_args.extend(keys) 332 | if isinstance(args, string_types): 333 | message_args.append(args) 334 | else: 335 | message_args.extend(args) 336 | self.send_message(message_args, callback) 337 | 338 | def evalsha(self, sha1, keys, args, callback=None): 339 | """ 340 | Execute a Lua script server side 341 | 342 | :param sha1: 343 | :param keys: 344 | string or list of strings 345 | :param args: 346 | string or list of strings 347 | 348 | Complexity 349 | ---------- 350 | Depends on the script that is executed. 351 | """ 352 | message_args = ["EVALSHA"] 353 | message_args.append(sha1) 354 | message_args.append(len(keys)) 355 | if isinstance(keys, string_types): 356 | message_args.append(keys) 357 | else: 358 | message_args.extend(keys) 359 | if isinstance(args, string_types): 360 | message_args.append(args) 361 | else: 362 | message_args.extend(args) 363 | self.send_message(message_args, callback) 364 | 365 | def execute(self, callback=None): 366 | """ 367 | Execute all commands issued after MULTI 368 | """ 369 | self.send_message(["EXEC"], callback) 370 | 371 | def exists(self, key, callback=None): 372 | """ 373 | Determine if a key exists 374 | 375 | :param key: 376 | 377 | Complexity 378 | ---------- 379 | O(1) 380 | """ 381 | args = ["EXISTS"] 382 | args.append(key) 383 | self.send_message(args, callback) 384 | 385 | def expire(self, key, seconds, callback=None): 386 | """ 387 | Set a key's time to live in seconds 388 | 389 | :param key: 390 | :param seconds: 391 | 392 | Complexity 393 | ---------- 394 | O(1) 395 | """ 396 | args = ["EXPIRE"] 397 | args.append(key) 398 | args.append(seconds) 399 | self.send_message(args, callback) 400 | 401 | def expireat(self, key, timestamp, callback=None): 402 | """ 403 | Set the expiration for a key as a UNIX timestamp 404 | 405 | :param key: 406 | :param timestamp: 407 | 408 | Complexity 409 | ---------- 410 | O(1) 411 | """ 412 | args = ["EXPIREAT"] 413 | args.append(key) 414 | args.append(timestamp) 415 | self.send_message(args, callback) 416 | 417 | def flushall(self, callback=None): 418 | """ 419 | Remove all keys from all databases 420 | """ 421 | self.send_message(["FLUSHALL"], callback) 422 | 423 | def flushdb(self, callback=None): 424 | """ 425 | Remove all keys from the current database 426 | """ 427 | self.send_message(["FLUSHDB"], callback) 428 | 429 | def get(self, key, callback=None): 430 | """ 431 | Get the value of a key 432 | 433 | :param key: 434 | 435 | Complexity 436 | ---------- 437 | O(1) 438 | """ 439 | args = ["GET"] 440 | args.append(key) 441 | self.send_message(args, callback) 442 | 443 | def getbit(self, key, offset, callback=None): 444 | """ 445 | Returns the bit value at offset in the string value stored at key 446 | 447 | :param key: 448 | :param offset: 449 | 450 | Complexity 451 | ---------- 452 | O(1) 453 | """ 454 | args = ["GETBIT"] 455 | args.append(key) 456 | args.append(offset) 457 | self.send_message(args, callback) 458 | 459 | def getrange(self, key, start, end, callback=None): 460 | """ 461 | Get a substring of the string stored at a key 462 | 463 | :param key: 464 | :param start: 465 | :param end: 466 | 467 | Complexity 468 | ---------- 469 | O(N) where N is the length of the returned string. The complexity is 470 | ultimately determined by the returned length, but because creating a 471 | substring from an existing string is very cheap, it can be considered 472 | O(1) for small strings. 473 | """ 474 | args = ["GETRANGE"] 475 | args.append(key) 476 | args.append(start) 477 | args.append(end) 478 | self.send_message(args, callback) 479 | 480 | def getset(self, key, value, callback=None): 481 | """ 482 | Set the string value of a key and return its old value 483 | 484 | :param key: 485 | :param value: 486 | 487 | Complexity 488 | ---------- 489 | O(1) 490 | """ 491 | args = ["GETSET"] 492 | args.append(key) 493 | args.append(value) 494 | self.send_message(args, callback) 495 | 496 | def hdel(self, key, fields, callback=None): 497 | """ 498 | Delete one or more hash fields 499 | 500 | :param key: 501 | :param fields: 502 | string or list of strings 503 | 504 | Complexity 505 | ---------- 506 | O(N) where N is the number of fields to be removed. 507 | """ 508 | args = ["HDEL"] 509 | args.append(key) 510 | if isinstance(fields, string_types): 511 | args.append(fields) 512 | else: 513 | args.extend(fields) 514 | self.send_message(args, callback) 515 | 516 | def hexists(self, key, field, callback=None): 517 | """ 518 | Determine if a hash field exists 519 | 520 | :param key: 521 | :param field: 522 | 523 | Complexity 524 | ---------- 525 | O(1) 526 | """ 527 | args = ["HEXISTS"] 528 | args.append(key) 529 | args.append(field) 530 | self.send_message(args, callback) 531 | 532 | def hget(self, key, field, callback=None): 533 | """ 534 | Get the value of a hash field 535 | 536 | :param key: 537 | :param field: 538 | 539 | Complexity 540 | ---------- 541 | O(1) 542 | """ 543 | args = ["HGET"] 544 | args.append(key) 545 | args.append(field) 546 | self.send_message(args, callback) 547 | 548 | def hgetall(self, key, callback=None): 549 | """ 550 | Get all the fields and values in a hash 551 | 552 | :param key: 553 | 554 | Complexity 555 | ---------- 556 | O(N) where N is the size of the hash. 557 | """ 558 | args = ["HGETALL"] 559 | args.append(key) 560 | self.send_message(args, callback) 561 | 562 | def hincrby(self, key, field, increment, callback=None): 563 | """ 564 | Increment the integer value of a hash field by the given number 565 | 566 | :param key: 567 | :param field: 568 | :param increment: 569 | 570 | Complexity 571 | ---------- 572 | O(1) 573 | """ 574 | args = ["HINCRBY"] 575 | args.append(key) 576 | args.append(field) 577 | args.append(increment) 578 | self.send_message(args, callback) 579 | 580 | def hincrbyfloat(self, key, field, increment, callback=None): 581 | """ 582 | Increment the float value of a hash field by the given amount 583 | 584 | :param key: 585 | :param field: 586 | :param increment: 587 | 588 | Complexity 589 | ---------- 590 | O(1) 591 | """ 592 | args = ["HINCRBYFLOAT"] 593 | args.append(key) 594 | args.append(field) 595 | args.append(increment) 596 | self.send_message(args, callback) 597 | 598 | def hkeys(self, key, callback=None): 599 | """ 600 | Get all the fields in a hash 601 | 602 | :param key: 603 | 604 | Complexity 605 | ---------- 606 | O(N) where N is the size of the hash. 607 | """ 608 | args = ["HKEYS"] 609 | args.append(key) 610 | self.send_message(args, callback) 611 | 612 | def hlen(self, key, callback=None): 613 | """ 614 | Get the number of fields in a hash 615 | 616 | :param key: 617 | 618 | Complexity 619 | ---------- 620 | O(1) 621 | """ 622 | args = ["HLEN"] 623 | args.append(key) 624 | self.send_message(args, callback) 625 | 626 | def hmget(self, key, fields, callback=None): 627 | """ 628 | Get the values of all the given hash fields 629 | 630 | :param key: 631 | :param fields: 632 | string or list of strings 633 | 634 | Complexity 635 | ---------- 636 | O(N) where N is the number of fields being requested. 637 | """ 638 | args = ["HMGET"] 639 | args.append(key) 640 | if isinstance(fields, string_types): 641 | args.append(fields) 642 | else: 643 | args.extend(fields) 644 | self.send_message(args, callback) 645 | 646 | def hmset(self, key, field_dict, callback=None): 647 | """ 648 | Set multiple hash fields to multiple values 649 | 650 | :param key: 651 | :param member_score_dict: 652 | key value dictionary 653 | 654 | Complexity 655 | ---------- 656 | O(N) where N is the number of fields being set. 657 | """ 658 | args = ["HMSET"] 659 | args.append(key) 660 | for field, value in field_dict.items(): 661 | args.append(field) 662 | args.append(value) 663 | self.send_message(args, callback) 664 | 665 | def hset(self, key, field, value, callback=None): 666 | """ 667 | Set the string value of a hash field 668 | 669 | :param key: 670 | :param field: 671 | :param value: 672 | 673 | Complexity 674 | ---------- 675 | O(1) 676 | """ 677 | args = ["HSET"] 678 | args.append(key) 679 | args.append(field) 680 | args.append(value) 681 | self.send_message(args, callback) 682 | 683 | def hsetnx(self, key, field, value, callback=None): 684 | """ 685 | Set the value of a hash field, only if the field does not exist 686 | 687 | :param key: 688 | :param field: 689 | :param value: 690 | 691 | Complexity 692 | ---------- 693 | O(1) 694 | """ 695 | args = ["HSETNX"] 696 | args.append(key) 697 | args.append(field) 698 | args.append(value) 699 | self.send_message(args, callback) 700 | 701 | def hvals(self, key, callback=None): 702 | """ 703 | Get all the values in a hash 704 | 705 | :param key: 706 | 707 | Complexity 708 | ---------- 709 | O(N) where N is the size of the hash. 710 | """ 711 | args = ["HVALS"] 712 | args.append(key) 713 | self.send_message(args, callback) 714 | 715 | def incr(self, key, callback=None): 716 | """ 717 | Increment the integer value of a key by one 718 | 719 | :param key: 720 | 721 | Complexity 722 | ---------- 723 | O(1) 724 | """ 725 | args = ["INCR"] 726 | args.append(key) 727 | self.send_message(args, callback) 728 | 729 | def incrby(self, key, increment, callback=None): 730 | """ 731 | Increment the integer value of a key by the given amount 732 | 733 | :param key: 734 | :param increment: 735 | 736 | Complexity 737 | ---------- 738 | O(1) 739 | """ 740 | args = ["INCRBY"] 741 | args.append(key) 742 | args.append(increment) 743 | self.send_message(args, callback) 744 | 745 | def incrbyfloat(self, key, increment, callback=None): 746 | """ 747 | Increment the float value of a key by the given amount 748 | 749 | :param key: 750 | :param increment: 751 | 752 | Complexity 753 | ---------- 754 | O(1) 755 | """ 756 | args = ["INCRBYFLOAT"] 757 | args.append(key) 758 | args.append(increment) 759 | self.send_message(args, callback) 760 | 761 | def info(self, callback=None): 762 | """ 763 | Get information and statistics about the server 764 | """ 765 | self.send_message(["INFO"], callback) 766 | 767 | def keys(self, pattern, callback=None): 768 | """ 769 | Find all keys matching the given pattern 770 | 771 | :param pattern: 772 | 773 | Complexity 774 | ---------- 775 | O(N) with N being the number of keys in the database, under the 776 | assumption that the key names in the database and the given pattern 777 | have limited length. 778 | """ 779 | args = ["KEYS"] 780 | args.append(pattern) 781 | self.send_message(args, callback) 782 | 783 | def lastsave(self, callback=None): 784 | """ 785 | Get the UNIX time stamp of the last successful save to disk 786 | """ 787 | self.send_message(["LASTSAVE"], callback) 788 | 789 | def lindex(self, key, index, callback=None): 790 | """ 791 | Get an element from a list by its index 792 | 793 | :param key: 794 | :param index: 795 | 796 | Complexity 797 | ---------- 798 | O(N) where N is the number of elements to traverse to get to the 799 | element at index. This makes asking for the first or the last element 800 | of the list O(1). 801 | """ 802 | args = ["LINDEX"] 803 | args.append(key) 804 | args.append(index) 805 | self.send_message(args, callback) 806 | 807 | def linsert(self, key, where, pivot, value, callback=None): 808 | """ 809 | Insert an element before or after another element in a list 810 | 811 | :param key: 812 | :param where: 813 | :param pivot: 814 | :param value: 815 | 816 | Complexity 817 | ---------- 818 | O(N) where N is the number of elements to traverse before seeing the 819 | value pivot. This means that inserting somewhere on the left end on 820 | the list (head) can be considered O(1) and inserting somewhere on the 821 | right end (tail) is O(N). 822 | """ 823 | args = ["LINSERT"] 824 | args.append(key) 825 | args.append(where) 826 | args.append(pivot) 827 | args.append(value) 828 | self.send_message(args, callback) 829 | 830 | def llen(self, key, callback=None): 831 | """ 832 | Get the length of a list 833 | 834 | :param key: 835 | 836 | Complexity 837 | ---------- 838 | O(1) 839 | """ 840 | args = ["LLEN"] 841 | args.append(key) 842 | self.send_message(args, callback) 843 | 844 | def lpop(self, key, callback=None): 845 | """ 846 | Remove and get the first element in a list 847 | 848 | :param key: 849 | 850 | Complexity 851 | ---------- 852 | O(1) 853 | """ 854 | args = ["LPOP"] 855 | args.append(key) 856 | self.send_message(args, callback) 857 | 858 | def lpush(self, key, values, callback=None): 859 | """ 860 | Prepend one or multiple values to a list 861 | 862 | :param key: 863 | :param values: 864 | string or list of strings 865 | 866 | Complexity 867 | ---------- 868 | O(1) 869 | """ 870 | args = ["LPUSH"] 871 | args.append(key) 872 | if isinstance(values, string_types): 873 | args.append(values) 874 | else: 875 | args.extend(values) 876 | self.send_message(args, callback) 877 | 878 | def lpushx(self, key, value, callback=None): 879 | """ 880 | Prepend a value to a list, only if the list exists 881 | 882 | :param key: 883 | :param value: 884 | 885 | Complexity 886 | ---------- 887 | O(1) 888 | """ 889 | args = ["LPUSHX"] 890 | args.append(key) 891 | args.append(value) 892 | self.send_message(args, callback) 893 | 894 | def lrange(self, key, start, stop, callback=None): 895 | """ 896 | Get a range of elements from a list 897 | 898 | :param key: 899 | :param start: 900 | :param stop: 901 | 902 | Complexity 903 | ---------- 904 | O(S+N) where S is the start offset and N is the number of elements in 905 | the specified range. 906 | """ 907 | args = ["LRANGE"] 908 | args.append(key) 909 | args.append(start) 910 | args.append(stop) 911 | self.send_message(args, callback) 912 | 913 | def lrem(self, key, count, value, callback=None): 914 | """ 915 | Remove elements from a list 916 | 917 | :param key: 918 | :param count: 919 | :param value: 920 | 921 | Complexity 922 | ---------- 923 | O(N) where N is the length of the list. 924 | """ 925 | args = ["LREM"] 926 | args.append(key) 927 | args.append(count) 928 | args.append(value) 929 | self.send_message(args, callback) 930 | 931 | def lset(self, key, index, value, callback=None): 932 | """ 933 | Set the value of an element in a list by its index 934 | 935 | :param key: 936 | :param index: 937 | :param value: 938 | 939 | Complexity 940 | ---------- 941 | O(N) where N is the length of the list. Setting either the first or 942 | the last element of the list is O(1). 943 | """ 944 | args = ["LSET"] 945 | args.append(key) 946 | args.append(index) 947 | args.append(value) 948 | self.send_message(args, callback) 949 | 950 | def ltrim(self, key, start, stop, callback=None): 951 | """ 952 | Trim a list to the specified range 953 | 954 | :param key: 955 | :param start: 956 | :param stop: 957 | 958 | Complexity 959 | ---------- 960 | O(N) where N is the number of elements to be removed by the operation. 961 | """ 962 | args = ["LTRIM"] 963 | args.append(key) 964 | args.append(start) 965 | args.append(stop) 966 | self.send_message(args, callback) 967 | 968 | def mget(self, keys, callback=None): 969 | """ 970 | Get the values of all the given keys 971 | 972 | :param keys: 973 | string or list of strings 974 | 975 | Complexity 976 | ---------- 977 | O(N) where N is the number of keys to retrieve. 978 | """ 979 | args = ["MGET"] 980 | if isinstance(keys, string_types): 981 | args.append(keys) 982 | else: 983 | args.extend(keys) 984 | self.send_message(args, callback) 985 | 986 | def migrate(self, host, port, key, destination_db, timeout, callback=None): 987 | """ 988 | Atomically transfer a key from a Redis instance to another one. 989 | 990 | :param host: 991 | :param port: 992 | :param key: 993 | :param destination_db: 994 | :param timeout: 995 | 996 | Complexity 997 | ---------- 998 | This command actually executes a DUMP+DEL in the source instance, and 999 | a RESTORE in the target instance. See the pages of these commands for 1000 | time complexity. Also an O(N) data transfer between the two instances 1001 | is performed. 1002 | """ 1003 | args = ["MIGRATE"] 1004 | args.append(host) 1005 | args.append(port) 1006 | args.append(key) 1007 | args.append(destination_db) 1008 | args.append(timeout) 1009 | self.send_message(args, callback) 1010 | 1011 | def monitor(self, callback=None): 1012 | """ 1013 | Listen for all requests received by the server in real time 1014 | """ 1015 | self.send_message(["MONITOR"], callback) 1016 | 1017 | def move(self, key, db, callback=None): 1018 | """ 1019 | Move a key to another database 1020 | 1021 | :param key: 1022 | :param db: 1023 | 1024 | Complexity 1025 | ---------- 1026 | O(1) 1027 | """ 1028 | args = ["MOVE"] 1029 | args.append(key) 1030 | args.append(db) 1031 | self.send_message(args, callback) 1032 | 1033 | def mset(self, key_dict, callback=None): 1034 | """ 1035 | Set multiple keys to multiple values 1036 | 1037 | :param member_score_dict: 1038 | key value dictionary 1039 | 1040 | Complexity 1041 | ---------- 1042 | O(N) where N is the number of keys to set. 1043 | """ 1044 | args = ["MSET"] 1045 | for key, value in key_dict.items(): 1046 | args.append(key) 1047 | args.append(value) 1048 | self.send_message(args, callback) 1049 | 1050 | def msetnx(self, key_dict, callback=None): 1051 | """ 1052 | Set multiple keys to multiple values, only if none of the keys exist 1053 | 1054 | :param member_score_dict: 1055 | key value dictionary 1056 | 1057 | Complexity 1058 | ---------- 1059 | O(N) where N is the number of keys to set. 1060 | """ 1061 | args = ["MSETNX"] 1062 | for key, value in key_dict.items(): 1063 | args.append(key) 1064 | args.append(value) 1065 | self.send_message(args, callback) 1066 | 1067 | def multi(self, callback=None): 1068 | """ 1069 | Mark the start of a transaction block 1070 | """ 1071 | self.send_message(["MULTI"], callback) 1072 | 1073 | def object(self, subcommand, argumentss=[], callback=None): 1074 | """ 1075 | Inspect the internals of Redis objects 1076 | 1077 | :param subcommand: 1078 | :param argumentss: 1079 | string or list of strings 1080 | 1081 | Complexity 1082 | ---------- 1083 | O(1) for all the currently implemented subcommands. 1084 | """ 1085 | args = ["OBJECT"] 1086 | args.append(subcommand) 1087 | if isinstance(argumentss, string_types): 1088 | args.append(argumentss) 1089 | else: 1090 | args.extend(argumentss) 1091 | self.send_message(args, callback) 1092 | 1093 | def persist(self, key, callback=None): 1094 | """ 1095 | Remove the expiration from a key 1096 | 1097 | :param key: 1098 | 1099 | Complexity 1100 | ---------- 1101 | O(1) 1102 | """ 1103 | args = ["PERSIST"] 1104 | args.append(key) 1105 | self.send_message(args, callback) 1106 | 1107 | def pexpire(self, key, milliseconds, callback=None): 1108 | """ 1109 | Set a key's time to live in milliseconds 1110 | 1111 | :param key: 1112 | :param milliseconds: 1113 | 1114 | Complexity 1115 | ---------- 1116 | O(1) 1117 | """ 1118 | args = ["PEXPIRE"] 1119 | args.append(key) 1120 | args.append(milliseconds) 1121 | self.send_message(args, callback) 1122 | 1123 | def pexpireat(self, key, milliseconds_timestamp, callback=None): 1124 | """ 1125 | Set the expiration for a key as a UNIX timestamp specified in 1126 | milliseconds 1127 | 1128 | :param key: 1129 | :param milliseconds_timestamp: 1130 | 1131 | Complexity 1132 | ---------- 1133 | O(1) 1134 | """ 1135 | args = ["PEXPIREAT"] 1136 | args.append(key) 1137 | args.append(milliseconds_timestamp) 1138 | self.send_message(args, callback) 1139 | 1140 | def ping(self, callback=None): 1141 | """ 1142 | Ping the server 1143 | """ 1144 | self.send_message(["PING"], callback) 1145 | 1146 | def psetex(self, key, milliseconds, value, callback=None): 1147 | """ 1148 | Set the value and expiration in milliseconds of a key 1149 | 1150 | :param key: 1151 | :param milliseconds: 1152 | :param value: 1153 | 1154 | Complexity 1155 | ---------- 1156 | O(1) 1157 | """ 1158 | args = ["PSETEX"] 1159 | args.append(key) 1160 | args.append(milliseconds) 1161 | args.append(value) 1162 | self.send_message(args, callback) 1163 | 1164 | def psubscribe(self, patterns, callback=None): 1165 | """ 1166 | Listen for messages published to channels matching the given patterns 1167 | 1168 | :param member_score_dict: 1169 | string or list of strings 1170 | 1171 | Complexity 1172 | ---------- 1173 | O(N) where N is the number of patterns the client is already 1174 | subscribed to. 1175 | """ 1176 | args = ["PSUBSCRIBE"] 1177 | if isinstance(patterns, string_types): 1178 | args.append(patterns) 1179 | else: 1180 | args.extend(patterns) 1181 | self.send_message(args, callback) 1182 | 1183 | def pttl(self, key, callback=None): 1184 | """ 1185 | Get the time to live for a key in milliseconds 1186 | 1187 | :param key: 1188 | 1189 | Complexity 1190 | ---------- 1191 | O(1) 1192 | """ 1193 | args = ["PTTL"] 1194 | args.append(key) 1195 | self.send_message(args, callback) 1196 | 1197 | def publish(self, channel, message, callback=None): 1198 | """ 1199 | Post a message to a channel 1200 | 1201 | :param channel: 1202 | :param message: 1203 | 1204 | Complexity 1205 | ---------- 1206 | O(N+M) where N is the number of clients subscribed to the receiving 1207 | channel and M is the total number of subscribed patterns (by any 1208 | client). 1209 | """ 1210 | args = ["PUBLISH"] 1211 | args.append(channel) 1212 | args.append(message) 1213 | self.send_message(args, callback) 1214 | 1215 | def punsubscribe(self, patterns=[], callback=None): 1216 | """ 1217 | Stop listening for messages posted to channels matching the given 1218 | patterns 1219 | 1220 | :param patterns: 1221 | string or list of strings 1222 | 1223 | Complexity 1224 | ---------- 1225 | O(N+M) where N is the number of patterns the client is already 1226 | subscribed and M is the number of total patterns subscribed in the 1227 | system (by any client). 1228 | """ 1229 | args = ["PUNSUBSCRIBE"] 1230 | if isinstance(patterns, string_types): 1231 | args.append(patterns) 1232 | else: 1233 | args.extend(patterns) 1234 | self.send_message(args, callback) 1235 | 1236 | def quit(self, callback=None): 1237 | """ 1238 | Close the connection 1239 | """ 1240 | self.send_message(["QUIT"], callback) 1241 | 1242 | def randomkey(self, callback=None): 1243 | """ 1244 | Return a random key from the keyspace 1245 | 1246 | Complexity 1247 | ---------- 1248 | O(1) 1249 | """ 1250 | self.send_message(["RANDOMKEY"], callback) 1251 | 1252 | def rename(self, key, newkey, callback=None): 1253 | """ 1254 | Rename a key 1255 | 1256 | :param key: 1257 | :param newkey: 1258 | 1259 | Complexity 1260 | ---------- 1261 | O(1) 1262 | """ 1263 | args = ["RENAME"] 1264 | args.append(key) 1265 | args.append(newkey) 1266 | self.send_message(args, callback) 1267 | 1268 | def renamenx(self, key, newkey, callback=None): 1269 | """ 1270 | Rename a key, only if the new key does not exist 1271 | 1272 | :param key: 1273 | :param newkey: 1274 | 1275 | Complexity 1276 | ---------- 1277 | O(1) 1278 | """ 1279 | args = ["RENAMENX"] 1280 | args.append(key) 1281 | args.append(newkey) 1282 | self.send_message(args, callback) 1283 | 1284 | def restore(self, key, ttl, serialized_value, callback=None): 1285 | """ 1286 | Create a key using the provided serialized value, previously obtained 1287 | using DUMP. 1288 | 1289 | :param key: 1290 | :param ttl: 1291 | :param serialized_value: 1292 | 1293 | Complexity 1294 | ---------- 1295 | O(1) to create the new key and additional O(N*M) to recostruct the 1296 | serialized value, where N is the number of Redis objects composing the 1297 | value and M their average size. For small string values the time 1298 | complexity is thus O(1)+O(1*M) where M is small, so simply O(1). 1299 | However for sorted set values the complexity is O(N*M*log(N)) because 1300 | inserting values into sorted sets is O(log(N)). 1301 | """ 1302 | args = ["RESTORE"] 1303 | args.append(key) 1304 | args.append(ttl) 1305 | args.append(serialized_value) 1306 | self.send_message(args, callback) 1307 | 1308 | def rpop(self, key, callback=None): 1309 | """ 1310 | Remove and get the last element in a list 1311 | 1312 | :param key: 1313 | 1314 | Complexity 1315 | ---------- 1316 | O(1) 1317 | """ 1318 | args = ["RPOP"] 1319 | args.append(key) 1320 | self.send_message(args, callback) 1321 | 1322 | def rpoplpush(self, source, destination, callback=None): 1323 | """ 1324 | Remove the last element in a list, append it to another list and 1325 | return it 1326 | 1327 | :param source: 1328 | :param destination: 1329 | 1330 | Complexity 1331 | ---------- 1332 | O(1) 1333 | """ 1334 | args = ["RPOPLPUSH"] 1335 | args.append(source) 1336 | args.append(destination) 1337 | self.send_message(args, callback) 1338 | 1339 | def rpush(self, key, values, callback=None): 1340 | """ 1341 | Append one or multiple values to a list 1342 | 1343 | :param key: 1344 | :param values: 1345 | string or list of strings 1346 | 1347 | Complexity 1348 | ---------- 1349 | O(1) 1350 | """ 1351 | args = ["RPUSH"] 1352 | args.append(key) 1353 | if isinstance(values, string_types): 1354 | args.append(values) 1355 | else: 1356 | args.extend(values) 1357 | self.send_message(args, callback) 1358 | 1359 | def rpushx(self, key, value, callback=None): 1360 | """ 1361 | Append a value to a list, only if the list exists 1362 | 1363 | :param key: 1364 | :param value: 1365 | 1366 | Complexity 1367 | ---------- 1368 | O(1) 1369 | """ 1370 | args = ["RPUSHX"] 1371 | args.append(key) 1372 | args.append(value) 1373 | self.send_message(args, callback) 1374 | 1375 | def sadd(self, key, members, callback=None): 1376 | """ 1377 | Add one or more members to a set 1378 | 1379 | :param key: 1380 | :param members: 1381 | string or list of strings 1382 | 1383 | Complexity 1384 | ---------- 1385 | O(N) where N is the number of members to be added. 1386 | """ 1387 | args = ["SADD"] 1388 | args.append(key) 1389 | if isinstance(members, string_types): 1390 | args.append(members) 1391 | else: 1392 | args.extend(members) 1393 | self.send_message(args, callback) 1394 | 1395 | def save(self, callback=None): 1396 | """ 1397 | Synchronously save the dataset to disk 1398 | """ 1399 | self.send_message(["SAVE"], callback) 1400 | 1401 | def scard(self, key, callback=None): 1402 | """ 1403 | Get the number of members in a set 1404 | 1405 | :param key: 1406 | 1407 | Complexity 1408 | ---------- 1409 | O(1) 1410 | """ 1411 | args = ["SCARD"] 1412 | args.append(key) 1413 | self.send_message(args, callback) 1414 | 1415 | def script_exists(self, scripts, callback=None): 1416 | """ 1417 | Check existence of scripts in the script cache. 1418 | 1419 | :param scripts: 1420 | string or list of strings 1421 | 1422 | Complexity 1423 | ---------- 1424 | O(N) with N being the number of scripts to check (so checking a single 1425 | script is an O(1) operation). 1426 | """ 1427 | args = ["SCRIPT", "EXISTS"] 1428 | if isinstance(scripts, string_types): 1429 | args.append(scripts) 1430 | else: 1431 | args.extend(scripts) 1432 | self.send_message(args, callback) 1433 | 1434 | def script_flush(self, callback=None): 1435 | """ 1436 | Remove all the scripts from the script cache. 1437 | 1438 | Complexity 1439 | ---------- 1440 | O(N) with N being the number of scripts in cache 1441 | """ 1442 | self.send_message(["SCRIPT", "FLUSH"], callback) 1443 | 1444 | def script_kill(self, callback=None): 1445 | """ 1446 | Kill the script currently in execution. 1447 | 1448 | Complexity 1449 | ---------- 1450 | O(1) 1451 | """ 1452 | self.send_message(["SCRIPT", "KILL"], callback) 1453 | 1454 | def script_load(self, script, callback=None): 1455 | """ 1456 | Load the specified Lua script into the script cache. 1457 | 1458 | :param script: 1459 | 1460 | Complexity 1461 | ---------- 1462 | O(N) with N being the length in bytes of the script body. 1463 | """ 1464 | args = ["SCRIPT", "LOAD"] 1465 | args.append(script) 1466 | self.send_message(args, callback) 1467 | 1468 | def sdiff(self, keys, callback=None): 1469 | """ 1470 | Subtract multiple sets 1471 | 1472 | :param keys: 1473 | string or list of strings 1474 | 1475 | Complexity 1476 | ---------- 1477 | O(N) where N is the total number of elements in all given sets. 1478 | """ 1479 | args = ["SDIFF"] 1480 | if isinstance(keys, string_types): 1481 | args.append(keys) 1482 | else: 1483 | args.extend(keys) 1484 | self.send_message(args, callback) 1485 | 1486 | def sdiffstore(self, destination, keys, callback=None): 1487 | """ 1488 | Subtract multiple sets and store the resulting set in a key 1489 | 1490 | :param destination: 1491 | :param keys: 1492 | string or list of strings 1493 | 1494 | Complexity 1495 | ---------- 1496 | O(N) where N is the total number of elements in all given sets. 1497 | """ 1498 | args = ["SDIFFSTORE"] 1499 | args.append(destination) 1500 | if isinstance(keys, string_types): 1501 | args.append(keys) 1502 | else: 1503 | args.extend(keys) 1504 | self.send_message(args, callback) 1505 | 1506 | def select(self, index, callback=None): 1507 | """ 1508 | Change the selected database for the current connection 1509 | 1510 | :param index: 1511 | """ 1512 | args = ["SELECT"] 1513 | args.append(index) 1514 | self.send_message(args, callback) 1515 | 1516 | def set(self, key, value, callback=None): 1517 | """ 1518 | Set the string value of a key 1519 | 1520 | :param key: 1521 | :param value: 1522 | 1523 | Complexity 1524 | ---------- 1525 | O(1) 1526 | """ 1527 | args = ["SET"] 1528 | args.append(key) 1529 | args.append(value) 1530 | self.send_message(args, callback) 1531 | 1532 | def setbit(self, key, offset, value, callback=None): 1533 | """ 1534 | Sets or clears the bit at offset in the string value stored at key 1535 | 1536 | :param key: 1537 | :param offset: 1538 | :param value: 1539 | 1540 | Complexity 1541 | ---------- 1542 | O(1) 1543 | """ 1544 | args = ["SETBIT"] 1545 | args.append(key) 1546 | args.append(offset) 1547 | args.append(value) 1548 | self.send_message(args, callback) 1549 | 1550 | def setex(self, key, seconds, value, callback=None): 1551 | """ 1552 | Set the value and expiration of a key 1553 | 1554 | :param key: 1555 | :param seconds: 1556 | :param value: 1557 | 1558 | Complexity 1559 | ---------- 1560 | O(1) 1561 | """ 1562 | args = ["SETEX"] 1563 | args.append(key) 1564 | args.append(seconds) 1565 | args.append(value) 1566 | self.send_message(args, callback) 1567 | 1568 | def setnx(self, key, value, callback=None): 1569 | """ 1570 | Set the value of a key, only if the key does not exist 1571 | 1572 | :param key: 1573 | :param value: 1574 | 1575 | Complexity 1576 | ---------- 1577 | O(1) 1578 | """ 1579 | args = ["SETNX"] 1580 | args.append(key) 1581 | args.append(value) 1582 | self.send_message(args, callback) 1583 | 1584 | def setrange(self, key, offset, value, callback=None): 1585 | """ 1586 | Overwrite part of a string at key starting at the specified offset 1587 | 1588 | :param key: 1589 | :param offset: 1590 | :param value: 1591 | 1592 | Complexity 1593 | ---------- 1594 | O(1), not counting the time taken to copy the new string in place. 1595 | Usually, this string is very small so the amortized complexity is 1596 | O(1). Otherwise, complexity is O(M) with M being the length of the 1597 | value argument. 1598 | """ 1599 | args = ["SETRANGE"] 1600 | args.append(key) 1601 | args.append(offset) 1602 | args.append(value) 1603 | self.send_message(args, callback) 1604 | 1605 | def shutdown(self, nosave=False, save=False, callback=None): 1606 | """ 1607 | Synchronously save the dataset to disk and then shut down the server 1608 | 1609 | :param nosave: 1610 | :param save: 1611 | """ 1612 | args = ["SHUTDOWN"] 1613 | if nosave: 1614 | args.append("NOSAVE") 1615 | if save: 1616 | args.append("SAVE") 1617 | self.send_message(args, callback) 1618 | 1619 | def sinter(self, keys, callback=None): 1620 | """ 1621 | Intersect multiple sets 1622 | 1623 | :param keys: 1624 | string or list of strings 1625 | 1626 | Complexity 1627 | ---------- 1628 | O(N*M) worst case where N is the cardinality of the smallest set and M 1629 | is the number of sets. 1630 | """ 1631 | args = ["SINTER"] 1632 | if isinstance(keys, string_types): 1633 | args.append(keys) 1634 | else: 1635 | args.extend(keys) 1636 | self.send_message(args, callback) 1637 | 1638 | def sinterstore(self, destination, keys, callback=None): 1639 | """ 1640 | Intersect multiple sets and store the resulting set in a key 1641 | 1642 | :param destination: 1643 | :param keys: 1644 | string or list of strings 1645 | 1646 | Complexity 1647 | ---------- 1648 | O(N*M) worst case where N is the cardinality of the smallest set and M 1649 | is the number of sets. 1650 | """ 1651 | args = ["SINTERSTORE"] 1652 | args.append(destination) 1653 | if isinstance(keys, string_types): 1654 | args.append(keys) 1655 | else: 1656 | args.extend(keys) 1657 | self.send_message(args, callback) 1658 | 1659 | def sismember(self, key, member, callback=None): 1660 | """ 1661 | Determine if a given value is a member of a set 1662 | 1663 | :param key: 1664 | :param member: 1665 | 1666 | Complexity 1667 | ---------- 1668 | O(1) 1669 | """ 1670 | args = ["SISMEMBER"] 1671 | args.append(key) 1672 | args.append(member) 1673 | self.send_message(args, callback) 1674 | 1675 | def slaveof(self, host, port, callback=None): 1676 | """ 1677 | Make the server a slave of another instance, or promote it as master 1678 | 1679 | :param host: 1680 | :param port: 1681 | """ 1682 | args = ["SLAVEOF"] 1683 | args.append(host) 1684 | args.append(port) 1685 | self.send_message(args, callback) 1686 | 1687 | def slowlog(self, subcommand, argument=None, callback=None): 1688 | """ 1689 | Manages the Redis slow queries log 1690 | 1691 | :param subcommand: 1692 | :param argument: 1693 | """ 1694 | args = ["SLOWLOG"] 1695 | args.append(subcommand) 1696 | args.append(argument) 1697 | self.send_message(args, callback) 1698 | 1699 | def smembers(self, key, callback=None): 1700 | """ 1701 | Get all the members in a set 1702 | 1703 | :param key: 1704 | 1705 | Complexity 1706 | ---------- 1707 | O(N) where N is the set cardinality. 1708 | """ 1709 | args = ["SMEMBERS"] 1710 | args.append(key) 1711 | self.send_message(args, callback) 1712 | 1713 | def smove(self, source, destination, member, callback=None): 1714 | """ 1715 | Move a member from one set to another 1716 | 1717 | :param source: 1718 | :param destination: 1719 | :param member: 1720 | 1721 | Complexity 1722 | ---------- 1723 | O(1) 1724 | """ 1725 | args = ["SMOVE"] 1726 | args.append(source) 1727 | args.append(destination) 1728 | args.append(member) 1729 | self.send_message(args, callback) 1730 | 1731 | def sort(self, key, by=None, limit=None, get=tuple(), order=None, sorting=False, store=None, callback=None): 1732 | """ 1733 | Sort the elements in a list, set or sorted set 1734 | 1735 | :param key: 1736 | :param by: 1737 | :param limit: 1738 | :param get: 1739 | :param order: 1740 | :param sorting: 1741 | :param store: 1742 | 1743 | Complexity 1744 | ---------- 1745 | O(N+M*log(M)) where N is the number of elements in the list or set to 1746 | sort, and M the number of returned elements. When the elements are not 1747 | sorted, complexity is currently O(N) as there is a copy step that will 1748 | be avoided in next releases. 1749 | """ 1750 | args = ["SORT"] 1751 | args.append(key) 1752 | if by: 1753 | args.append("BY") 1754 | args.append(by) 1755 | if limit: 1756 | args.append("LIMIT") 1757 | offset, count = limit 1758 | args.append(offset) 1759 | args.append(count) 1760 | for pattern in get: 1761 | args.append("GET") 1762 | args.append(pattern) 1763 | args.append(order) 1764 | if sorting: 1765 | args.append("ALPHA") 1766 | if store: 1767 | args.append("STORE") 1768 | args.append(store) 1769 | self.send_message(args, callback) 1770 | 1771 | def spop(self, key, callback=None): 1772 | """ 1773 | Remove and return a random member from a set 1774 | 1775 | :param key: 1776 | 1777 | Complexity 1778 | ---------- 1779 | O(1) 1780 | """ 1781 | args = ["SPOP"] 1782 | args.append(key) 1783 | self.send_message(args, callback) 1784 | 1785 | def srandmember(self, key, count=None, callback=None): 1786 | """ 1787 | Get one or multiple random members from a set 1788 | 1789 | :param key: 1790 | :param count: 1791 | 1792 | Complexity 1793 | ---------- 1794 | Without the count argument O(1), otherwise O(N) where N is the 1795 | absolute value of the passed count. 1796 | """ 1797 | args = ["SRANDMEMBER"] 1798 | args.append(key) 1799 | args.append(count) 1800 | self.send_message(args, callback) 1801 | 1802 | def srem(self, key, members, callback=None): 1803 | """ 1804 | Remove one or more members from a set 1805 | 1806 | :param key: 1807 | :param members: 1808 | string or list of strings 1809 | 1810 | Complexity 1811 | ---------- 1812 | O(N) where N is the number of members to be removed. 1813 | """ 1814 | args = ["SREM"] 1815 | args.append(key) 1816 | if isinstance(members, string_types): 1817 | args.append(members) 1818 | else: 1819 | args.extend(members) 1820 | self.send_message(args, callback) 1821 | 1822 | def strlen(self, key, callback=None): 1823 | """ 1824 | Get the length of the value stored in a key 1825 | 1826 | :param key: 1827 | 1828 | Complexity 1829 | ---------- 1830 | O(1) 1831 | """ 1832 | args = ["STRLEN"] 1833 | args.append(key) 1834 | self.send_message(args, callback) 1835 | 1836 | def subscribe(self, channels, callback=None): 1837 | """ 1838 | Listen for messages published to the given channels 1839 | 1840 | :param member_score_dict: 1841 | string or list of strings 1842 | 1843 | Complexity 1844 | ---------- 1845 | O(N) where N is the number of channels to subscribe to. 1846 | """ 1847 | args = ["SUBSCRIBE"] 1848 | if isinstance(channels, string_types): 1849 | args.append(channels) 1850 | else: 1851 | args.extend(channels) 1852 | self.send_message(args, callback) 1853 | 1854 | def sunion(self, keys, callback=None): 1855 | """ 1856 | Add multiple sets 1857 | 1858 | :param keys: 1859 | string or list of strings 1860 | 1861 | Complexity 1862 | ---------- 1863 | O(N) where N is the total number of elements in all given sets. 1864 | """ 1865 | args = ["SUNION"] 1866 | if isinstance(keys, string_types): 1867 | args.append(keys) 1868 | else: 1869 | args.extend(keys) 1870 | self.send_message(args, callback) 1871 | 1872 | def sunionstore(self, destination, keys, callback=None): 1873 | """ 1874 | Add multiple sets and store the resulting set in a key 1875 | 1876 | :param destination: 1877 | :param keys: 1878 | string or list of strings 1879 | 1880 | Complexity 1881 | ---------- 1882 | O(N) where N is the total number of elements in all given sets. 1883 | """ 1884 | args = ["SUNIONSTORE"] 1885 | args.append(destination) 1886 | if isinstance(keys, string_types): 1887 | args.append(keys) 1888 | else: 1889 | args.extend(keys) 1890 | self.send_message(args, callback) 1891 | 1892 | def sync(self, callback=None): 1893 | """ 1894 | Internal command used for replication 1895 | """ 1896 | self.send_message(["SYNC"], callback) 1897 | 1898 | def time(self, callback=None): 1899 | """ 1900 | Return the current server time 1901 | 1902 | Complexity 1903 | ---------- 1904 | O(1) 1905 | """ 1906 | self.send_message(["TIME"], callback) 1907 | 1908 | def ttl(self, key, callback=None): 1909 | """ 1910 | Get the time to live for a key 1911 | 1912 | :param key: 1913 | 1914 | Complexity 1915 | ---------- 1916 | O(1) 1917 | """ 1918 | args = ["TTL"] 1919 | args.append(key) 1920 | self.send_message(args, callback) 1921 | 1922 | def type(self, key, callback=None): 1923 | """ 1924 | Determine the type stored at key 1925 | 1926 | :param key: 1927 | 1928 | Complexity 1929 | ---------- 1930 | O(1) 1931 | """ 1932 | args = ["TYPE"] 1933 | args.append(key) 1934 | self.send_message(args, callback) 1935 | 1936 | def unsubscribe(self, channels=[], callback=None): 1937 | """ 1938 | Stop listening for messages posted to the given channels 1939 | 1940 | :param channels: 1941 | string or list of strings 1942 | 1943 | Complexity 1944 | ---------- 1945 | O(N) where N is the number of clients already subscribed to a channel. 1946 | """ 1947 | args = ["UNSUBSCRIBE"] 1948 | if isinstance(channels, string_types): 1949 | args.append(channels) 1950 | else: 1951 | args.extend(channels) 1952 | self.send_message(args, callback) 1953 | 1954 | def unwatch(self, callback=None): 1955 | """ 1956 | Forget about all watched keys 1957 | 1958 | Complexity 1959 | ---------- 1960 | O(1) 1961 | """ 1962 | self.send_message(["UNWATCH"], callback) 1963 | 1964 | def watch(self, keys, callback=None): 1965 | """ 1966 | Watch the given keys to determine execution of the MULTI/EXEC block 1967 | 1968 | :param keys: 1969 | string or list of strings 1970 | 1971 | Complexity 1972 | ---------- 1973 | O(1) for every key. 1974 | """ 1975 | args = ["WATCH"] 1976 | if isinstance(keys, string_types): 1977 | args.append(keys) 1978 | else: 1979 | args.extend(keys) 1980 | self.send_message(args, callback) 1981 | 1982 | def zadd(self, key, member_score_dict, callback=None): 1983 | """ 1984 | Add one or more members to a sorted set, or update its score if it 1985 | already exists 1986 | 1987 | :param key: 1988 | :param member_score_dict: 1989 | member score dictionary 1990 | 1991 | Complexity 1992 | ---------- 1993 | O(log(N)) where N is the number of elements in the sorted set. 1994 | """ 1995 | args = ["ZADD"] 1996 | args.append(key) 1997 | for member, score in member_score_dict.items(): 1998 | args.append(score) 1999 | args.append(member) 2000 | self.send_message(args, callback) 2001 | 2002 | def zcard(self, key, callback=None): 2003 | """ 2004 | Get the number of members in a sorted set 2005 | 2006 | :param key: 2007 | 2008 | Complexity 2009 | ---------- 2010 | O(1) 2011 | """ 2012 | args = ["ZCARD"] 2013 | args.append(key) 2014 | self.send_message(args, callback) 2015 | 2016 | def zcount(self, key, min, max, callback=None): 2017 | """ 2018 | Count the members in a sorted set with scores within the given values 2019 | 2020 | :param key: 2021 | :param min: 2022 | :param max: 2023 | 2024 | Complexity 2025 | ---------- 2026 | O(log(N)+M) with N being the number of elements in the sorted set and 2027 | M being the number of elements between min and max. 2028 | """ 2029 | args = ["ZCOUNT"] 2030 | args.append(key) 2031 | args.append(min) 2032 | args.append(max) 2033 | self.send_message(args, callback) 2034 | 2035 | def zincrby(self, key, increment, member, callback=None): 2036 | """ 2037 | Increment the score of a member in a sorted set 2038 | 2039 | :param key: 2040 | :param increment: 2041 | :param member: 2042 | 2043 | Complexity 2044 | ---------- 2045 | O(log(N)) where N is the number of elements in the sorted set. 2046 | """ 2047 | args = ["ZINCRBY"] 2048 | args.append(key) 2049 | args.append(increment) 2050 | args.append(member) 2051 | self.send_message(args, callback) 2052 | 2053 | def zinterstore(self, destination, keys, weights=tuple(), aggregate=None, callback=None): 2054 | """ 2055 | Intersect multiple sorted sets and store the resulting sorted set in a 2056 | new key 2057 | 2058 | :param destination: 2059 | :param keys: 2060 | string or list of strings 2061 | :param weights: 2062 | :param aggregate: 2063 | 2064 | Complexity 2065 | ---------- 2066 | O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted 2067 | set, K being the number of input sorted sets and M being the number of 2068 | elements in the resulting sorted set. 2069 | """ 2070 | args = ["ZINTERSTORE"] 2071 | args.append(destination) 2072 | args.append(len(keys)) 2073 | if isinstance(keys, string_types): 2074 | args.append(keys) 2075 | else: 2076 | args.extend(keys) 2077 | if len(weights): 2078 | args.append("WEIGHTS") 2079 | args.extend(weights) 2080 | if aggregate: 2081 | args.append("AGGREGATE") 2082 | args.append(aggregate) 2083 | self.send_message(args, callback) 2084 | 2085 | def zrange(self, key, start, stop, withscores=False, callback=None): 2086 | """ 2087 | Return a range of members in a sorted set, by index 2088 | 2089 | :param key: 2090 | :param start: 2091 | :param stop: 2092 | :param withscores: 2093 | 2094 | Complexity 2095 | ---------- 2096 | O(log(N)+M) with N being the number of elements in the sorted set and 2097 | M the number of elements returned. 2098 | """ 2099 | args = ["ZRANGE"] 2100 | args.append(key) 2101 | args.append(start) 2102 | args.append(stop) 2103 | if withscores: 2104 | args.append("WITHSCORES") 2105 | self.send_message(args, callback) 2106 | 2107 | def zrangebyscore(self, key, min, max, withscores=False, limit=None, callback=None): 2108 | """ 2109 | Return a range of members in a sorted set, by score 2110 | 2111 | :param key: 2112 | :param min: 2113 | :param max: 2114 | :param withscores: 2115 | :param limit: 2116 | 2117 | Complexity 2118 | ---------- 2119 | O(log(N)+M) with N being the number of elements in the sorted set and 2120 | M the number of elements being returned. If M is constant (e.g. always 2121 | asking for the first 10 elements with LIMIT), you can consider it 2122 | O(log(N)). 2123 | """ 2124 | args = ["ZRANGEBYSCORE"] 2125 | args.append(key) 2126 | args.append(min) 2127 | args.append(max) 2128 | if withscores: 2129 | args.append("WITHSCORES") 2130 | if limit: 2131 | args.append("LIMIT") 2132 | offset, count = limit 2133 | args.append(offset) 2134 | args.append(count) 2135 | self.send_message(args, callback) 2136 | 2137 | def zrank(self, key, member, callback=None): 2138 | """ 2139 | Determine the index of a member in a sorted set 2140 | 2141 | :param key: 2142 | :param member: 2143 | 2144 | Complexity 2145 | ---------- 2146 | O(log(N)) 2147 | """ 2148 | args = ["ZRANK"] 2149 | args.append(key) 2150 | args.append(member) 2151 | self.send_message(args, callback) 2152 | 2153 | def zrem(self, key, members, callback=None): 2154 | """ 2155 | Remove one or more members from a sorted set 2156 | 2157 | :param key: 2158 | :param members: 2159 | string or list of strings 2160 | 2161 | Complexity 2162 | ---------- 2163 | O(M*log(N)) with N being the number of elements in the sorted set and 2164 | M the number of elements to be removed. 2165 | """ 2166 | args = ["ZREM"] 2167 | args.append(key) 2168 | if isinstance(members, string_types): 2169 | args.append(members) 2170 | else: 2171 | args.extend(members) 2172 | self.send_message(args, callback) 2173 | 2174 | def zremrangebyrank(self, key, start, stop, callback=None): 2175 | """ 2176 | Remove all members in a sorted set within the given indexes 2177 | 2178 | :param key: 2179 | :param start: 2180 | :param stop: 2181 | 2182 | Complexity 2183 | ---------- 2184 | O(log(N)+M) with N being the number of elements in the sorted set and 2185 | M the number of elements removed by the operation. 2186 | """ 2187 | args = ["ZREMRANGEBYRANK"] 2188 | args.append(key) 2189 | args.append(start) 2190 | args.append(stop) 2191 | self.send_message(args, callback) 2192 | 2193 | def zremrangebyscore(self, key, min, max, callback=None): 2194 | """ 2195 | Remove all members in a sorted set within the given scores 2196 | 2197 | :param key: 2198 | :param min: 2199 | :param max: 2200 | 2201 | Complexity 2202 | ---------- 2203 | O(log(N)+M) with N being the number of elements in the sorted set and 2204 | M the number of elements removed by the operation. 2205 | """ 2206 | args = ["ZREMRANGEBYSCORE"] 2207 | args.append(key) 2208 | args.append(min) 2209 | args.append(max) 2210 | self.send_message(args, callback) 2211 | 2212 | def zrevrange(self, key, start, stop, withscores=False, callback=None): 2213 | """ 2214 | Return a range of members in a sorted set, by index, with scores 2215 | ordered from high to low 2216 | 2217 | :param key: 2218 | :param start: 2219 | :param stop: 2220 | :param withscores: 2221 | 2222 | Complexity 2223 | ---------- 2224 | O(log(N)+M) with N being the number of elements in the sorted set and 2225 | M the number of elements returned. 2226 | """ 2227 | args = ["ZREVRANGE"] 2228 | args.append(key) 2229 | args.append(start) 2230 | args.append(stop) 2231 | if withscores: 2232 | args.append("WITHSCORES") 2233 | self.send_message(args, callback) 2234 | 2235 | def zrevrangebyscore(self, key, max, min, withscores=False, limit=None, callback=None): 2236 | """ 2237 | Return a range of members in a sorted set, by score, with scores 2238 | ordered from high to low 2239 | 2240 | :param key: 2241 | :param max: 2242 | :param min: 2243 | :param withscores: 2244 | :param limit: 2245 | 2246 | Complexity 2247 | ---------- 2248 | O(log(N)+M) with N being the number of elements in the sorted set and 2249 | M the number of elements being returned. If M is constant (e.g. always 2250 | asking for the first 10 elements with LIMIT), you can consider it 2251 | O(log(N)). 2252 | """ 2253 | args = ["ZREVRANGEBYSCORE"] 2254 | args.append(key) 2255 | args.append(max) 2256 | args.append(min) 2257 | if withscores: 2258 | args.append("WITHSCORES") 2259 | if limit: 2260 | args.append("LIMIT") 2261 | offset, count = limit 2262 | args.append(offset) 2263 | args.append(count) 2264 | self.send_message(args, callback) 2265 | 2266 | def zrevrank(self, key, member, callback=None): 2267 | """ 2268 | Determine the index of a member in a sorted set, with scores ordered 2269 | from high to low 2270 | 2271 | :param key: 2272 | :param member: 2273 | 2274 | Complexity 2275 | ---------- 2276 | O(log(N)) 2277 | """ 2278 | args = ["ZREVRANK"] 2279 | args.append(key) 2280 | args.append(member) 2281 | self.send_message(args, callback) 2282 | 2283 | def zscore(self, key, member, callback=None): 2284 | """ 2285 | Get the score associated with the given member in a sorted set 2286 | 2287 | :param key: 2288 | :param member: 2289 | 2290 | Complexity 2291 | ---------- 2292 | O(1) 2293 | """ 2294 | args = ["ZSCORE"] 2295 | args.append(key) 2296 | args.append(member) 2297 | self.send_message(args, callback) 2298 | 2299 | def zunionstore(self, destination, keys, weights=tuple(), aggregate=None, callback=None): 2300 | """ 2301 | Add multiple sorted sets and store the resulting sorted set in a new 2302 | key 2303 | 2304 | :param destination: 2305 | :param keys: 2306 | string or list of strings 2307 | :param weights: 2308 | :param aggregate: 2309 | 2310 | Complexity 2311 | ---------- 2312 | O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted 2313 | sets, and M being the number of elements in the resulting sorted set. 2314 | """ 2315 | args = ["ZUNIONSTORE"] 2316 | args.append(destination) 2317 | args.append(len(keys)) 2318 | if isinstance(keys, string_types): 2319 | args.append(keys) 2320 | else: 2321 | args.extend(keys) 2322 | if len(weights): 2323 | args.append("WEIGHTS") 2324 | args.extend(weights) 2325 | if aggregate: 2326 | args.append("AGGREGATE") 2327 | args.append(aggregate) 2328 | self.send_message(args, callback) 2329 | -------------------------------------------------------------------------------- /toredis/pipeline.py: -------------------------------------------------------------------------------- 1 | from toredis.commands import RedisCommandsMixin 2 | 3 | 4 | class Pipeline(RedisCommandsMixin): 5 | """ 6 | Redis pipeline class 7 | """ 8 | def __init__(self, client): 9 | """ 10 | Constructor 11 | 12 | :param client: 13 | Client instance 14 | """ 15 | 16 | self._client = client 17 | self._args_pipeline = [] 18 | 19 | def send_message(self, args, callback=None): 20 | """ 21 | Add command to pipeline 22 | 23 | :param args: 24 | Command arguments 25 | :param callback: 26 | Callback 27 | """ 28 | self._args_pipeline.append(args) 29 | 30 | def send(self, callback=None): 31 | """ 32 | Send command pipeline to redis 33 | 34 | :param callback: 35 | Callback 36 | """ 37 | args_pipeline = self._args_pipeline 38 | self._args_pipeline = [] 39 | self._client.send_messages(args_pipeline, callback) 40 | 41 | def reset(self): 42 | """ 43 | Reset command pipeline 44 | """ 45 | self._args_pipeline = [] 46 | --------------------------------------------------------------------------------