├── .gitignore ├── aggregate-pattern └── js │ ├── .env.example │ ├── after.js │ ├── before.js │ ├── client.js │ ├── data.json │ ├── import_after.js │ ├── import_before.js │ ├── package-lock.json │ ├── package.json │ └── readme.md ├── bucket-pattern └── js │ ├── .env.example │ ├── client.js │ ├── data.zip │ ├── demo.js │ ├── generate.js │ ├── package-lock.json │ ├── package.json │ └── readme.md ├── docker-compose.yml ├── polymorphic-pattern └── js │ ├── .env.example │ ├── client.js │ ├── data.json │ ├── demo.js │ ├── import.js │ ├── package-lock.json │ └── package.json ├── relationships └── python │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── api │ ├── api.py │ ├── generate.py │ ├── main.py │ ├── models.py │ └── utils.py │ ├── many-to-many │ ├── main.py │ └── utils.py │ ├── one-to-many │ ├── partial.py │ ├── separate.py │ └── utils.py │ ├── one-to-one │ ├── embedded.py │ ├── separate.py │ └── utils.py │ ├── poetry.lock │ └── pyproject.toml ├── revision-pattern └── python │ ├── .env.example │ ├── demo.py │ ├── package.json │ ├── poetry.lock │ ├── pyproject.toml │ └── utils.py ├── schema-version-pattern └── js │ ├── .env.example │ ├── client.js │ ├── package-lock.json │ ├── package.json │ ├── part1.js │ └── part2.js └── tree-and-graph-pattern ├── README.md ├── data ├── Employee.csv ├── Location.csv ├── REPORTS_TO.csv └── WORKS_AT.csv └── queries.redis /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.env 3 | node_modules 4 | *.exe 5 | -------------------------------------------------------------------------------- /aggregate-pattern/js/.env.example: -------------------------------------------------------------------------------- 1 | REDIS_URL=redis://localhost:6379 -------------------------------------------------------------------------------- /aggregate-pattern/js/after.js: -------------------------------------------------------------------------------- 1 | import { Schema, Repository } from 'redis-om'; 2 | import { getClient } from './client.js'; 3 | 4 | const productSchema = new Schema('Product', { 5 | name: { type: 'string' }, 6 | numReviews: { type: 'number' }, 7 | sumRatings: { type: 'number' }, 8 | }); 9 | 10 | const productReviewSchema = new Schema('ProductReview', { 11 | productId: { type: 'string' }, 12 | author: { type: 'string' }, 13 | rating: { type: 'number' }, 14 | }); 15 | 16 | async function addReview(productId, author, rating) { 17 | const client = await getClient(); 18 | const productRepo = new Repository(productSchema, client); 19 | const productReviewRepo = new Repository(productReviewSchema, client); 20 | const productEntity = await productRepo.fetch(productId); 21 | 22 | // @ts-ignore 23 | productEntity.numReviews += 1; 24 | productEntity.sumRatings += rating; 25 | 26 | return Promise.all([ 27 | productRepo.save(productEntity), 28 | productReviewRepo.save({ 29 | productId, 30 | author, 31 | rating, 32 | }), 33 | ]); 34 | } 35 | 36 | async function getProducts() { 37 | const client = await getClient(); 38 | const productRepo = new Repository(productSchema, client); 39 | 40 | return productRepo.search().return.all(); 41 | } 42 | 43 | console.log(await getProducts()); 44 | 45 | process.exit(1); 46 | -------------------------------------------------------------------------------- /aggregate-pattern/js/before.js: -------------------------------------------------------------------------------- 1 | import { Schema, Repository, EntityId } from 'redis-om'; 2 | import { getClient } from './client.js'; 3 | import { AggregateGroupByReducers, AggregateSteps } from 'redis'; 4 | 5 | const productSchema = new Schema('Product', { 6 | name: { type: 'string' }, 7 | }); 8 | 9 | const productReviewSchema = new Schema('ProductReview', { 10 | productId: { type: 'string' }, 11 | author: { type: 'string' }, 12 | rating: { type: 'number' }, 13 | }); 14 | 15 | async function addReview(productId, author, rating) { 16 | const client = await getClient(); 17 | const productReviews = new Repository(productReviewSchema, client); 18 | await productReviews.save({ 19 | productId, 20 | author, 21 | rating, 22 | }); 23 | } 24 | 25 | async function getProducts() { 26 | const client = await getClient(); 27 | const productRepo = new Repository(productSchema, client); 28 | const productEntities = await productRepo.search().return.all(); 29 | const { results } = await client.ft.aggregate( 30 | 'ProductReview:index', 31 | '*', 32 | { 33 | STEPS: [ 34 | { 35 | type: AggregateSteps.GROUPBY, 36 | properties: '@productId', 37 | REDUCE: [{ 38 | property: '@rating', 39 | type: AggregateGroupByReducers.AVG, 40 | AS: 'avgRating' 41 | }, { 42 | type: AggregateGroupByReducers.COUNT, 43 | AS: 'numReviews' 44 | }], 45 | } 46 | ], 47 | } 48 | ); 49 | 50 | const products = {}; 51 | for (let { productId, avgRating, numReviews } of results) { 52 | products[productId] = { 53 | avgRating: Number(avgRating), 54 | numReviews: Number(numReviews), 55 | }; 56 | } 57 | 58 | return productEntities.map((entity) => { 59 | return { 60 | id: entity[EntityId], 61 | ...entity, 62 | ...products[entity[EntityId]], 63 | }; 64 | }); 65 | } 66 | 67 | console.log(await getProducts()); 68 | 69 | process.exit(1); -------------------------------------------------------------------------------- /aggregate-pattern/js/client.js: -------------------------------------------------------------------------------- 1 | import { createClient } from 'redis'; 2 | import { config } from 'dotenv'; 3 | 4 | config(); 5 | 6 | const clientPromise = new Promise(async (resolve) => { 7 | const client = createClient({ 8 | url: process.env.REDIS_URL, 9 | }); 10 | 11 | client.on('error', (err) => console.log('Redis Client Error', err)); 12 | await client.connect(); 13 | 14 | resolve(client); 15 | }); 16 | 17 | /** 18 | * 19 | * @returns {Promise} 20 | */ 21 | export async function getClient() { 22 | return clientPromise; 23 | } 24 | -------------------------------------------------------------------------------- /aggregate-pattern/js/import_after.js: -------------------------------------------------------------------------------- 1 | import { Schema, Repository, EntityId } from 'redis-om'; 2 | import { getClient } from './client.js'; 3 | const { default: data } = await import('./data.json', { 4 | assert: { type: 'json' }, 5 | }); 6 | 7 | const productSchema = new Schema('Product', { 8 | name: { type: 'string' }, 9 | numReviews: { type: 'number' }, 10 | sumRatings: { type: 'number' }, 11 | }); 12 | 13 | const productReviewSchema = new Schema('ProductReview', { 14 | productId: { type: 'string' }, 15 | author: { type: 'string' }, 16 | rating: { type: 'number' }, 17 | }); 18 | 19 | const client = await getClient(); 20 | await client.flushDb(); 21 | const products = new Repository(productSchema, client); 22 | const productReviews = new Repository(productReviewSchema, client); 23 | await products.createIndex(); 24 | await productReviews.createIndex(); 25 | 26 | for (let product of data) { 27 | const numReviews = product.reviews.length; 28 | const productEntity = await products.save({ 29 | name: product.name, 30 | numReviews, 31 | sumRatings: 0, 32 | }); 33 | let sum = 0; 34 | for (let review of product.reviews) { 35 | sum += review.rating; 36 | await productReviews.save({ 37 | productId: productEntity[EntityId], 38 | ...review, 39 | }); 40 | } 41 | 42 | productEntity.sumRatings = sum; 43 | 44 | await products.save(productEntity); 45 | } 46 | 47 | process.exit(1); 48 | -------------------------------------------------------------------------------- /aggregate-pattern/js/import_before.js: -------------------------------------------------------------------------------- 1 | import { Schema, Repository, EntityId } from 'redis-om'; 2 | import { getClient } from './client.js'; 3 | const { default: data } = await import('./data.json', { 4 | assert: { type: 'json' }, 5 | }); 6 | 7 | const productSchema = new Schema('Product', { 8 | name: { type: 'string' }, 9 | }); 10 | 11 | const productReviewSchema = new Schema('ProductReview', { 12 | productId: { type: 'string' }, 13 | author: { type: 'string' }, 14 | rating: { type: 'number' }, 15 | }); 16 | 17 | const client = await getClient(); 18 | await client.flushDb(); 19 | const products = new Repository(productSchema, client); 20 | const productReviews = new Repository(productReviewSchema, client); 21 | await products.createIndex(); 22 | await productReviews.createIndex(); 23 | 24 | for (let product of data) { 25 | const productEntity = await products.save({ name: product.name }); 26 | for (let review of product.reviews) { 27 | await productReviews.save({ 28 | productId: productEntity[EntityId], 29 | ...review 30 | }); 31 | } 32 | } 33 | 34 | 35 | process.exit(1); 36 | -------------------------------------------------------------------------------- /aggregate-pattern/js/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aggregate-pattern", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "aggregate-pattern", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dotenv": "^16.1.4", 13 | "redis": "^4.6.7", 14 | "redis-om": "^0.4.0-beta.3" 15 | }, 16 | "engines": { 17 | "node": ">=17" 18 | } 19 | }, 20 | "node_modules/@redis/bloom": { 21 | "version": "1.2.0", 22 | "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", 23 | "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", 24 | "peerDependencies": { 25 | "@redis/client": "^1.0.0" 26 | } 27 | }, 28 | "node_modules/@redis/client": { 29 | "version": "1.5.8", 30 | "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz", 31 | "integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==", 32 | "dependencies": { 33 | "cluster-key-slot": "1.1.2", 34 | "generic-pool": "3.9.0", 35 | "yallist": "4.0.0" 36 | }, 37 | "engines": { 38 | "node": ">=14" 39 | } 40 | }, 41 | "node_modules/@redis/graph": { 42 | "version": "1.1.0", 43 | "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", 44 | "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", 45 | "peerDependencies": { 46 | "@redis/client": "^1.0.0" 47 | } 48 | }, 49 | "node_modules/@redis/json": { 50 | "version": "1.0.4", 51 | "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", 52 | "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", 53 | "peerDependencies": { 54 | "@redis/client": "^1.0.0" 55 | } 56 | }, 57 | "node_modules/@redis/search": { 58 | "version": "1.1.3", 59 | "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz", 60 | "integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==", 61 | "peerDependencies": { 62 | "@redis/client": "^1.0.0" 63 | } 64 | }, 65 | "node_modules/@redis/time-series": { 66 | "version": "1.0.4", 67 | "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", 68 | "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", 69 | "peerDependencies": { 70 | "@redis/client": "^1.0.0" 71 | } 72 | }, 73 | "node_modules/cluster-key-slot": { 74 | "version": "1.1.2", 75 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", 76 | "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", 77 | "engines": { 78 | "node": ">=0.10.0" 79 | } 80 | }, 81 | "node_modules/dotenv": { 82 | "version": "16.1.4", 83 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", 84 | "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==", 85 | "engines": { 86 | "node": ">=12" 87 | }, 88 | "funding": { 89 | "url": "https://github.com/motdotla/dotenv?sponsor=1" 90 | } 91 | }, 92 | "node_modules/generic-pool": { 93 | "version": "3.9.0", 94 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", 95 | "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", 96 | "engines": { 97 | "node": ">= 4" 98 | } 99 | }, 100 | "node_modules/jsonpath-plus": { 101 | "version": "7.2.0", 102 | "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz", 103 | "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==", 104 | "engines": { 105 | "node": ">=12.0.0" 106 | } 107 | }, 108 | "node_modules/just-clone": { 109 | "version": "6.2.0", 110 | "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-6.2.0.tgz", 111 | "integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==" 112 | }, 113 | "node_modules/redis": { 114 | "version": "4.6.7", 115 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", 116 | "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==", 117 | "dependencies": { 118 | "@redis/bloom": "1.2.0", 119 | "@redis/client": "1.5.8", 120 | "@redis/graph": "1.1.0", 121 | "@redis/json": "1.0.4", 122 | "@redis/search": "1.1.3", 123 | "@redis/time-series": "1.0.4" 124 | } 125 | }, 126 | "node_modules/redis-om": { 127 | "version": "0.4.0-beta.3", 128 | "resolved": "https://registry.npmjs.org/redis-om/-/redis-om-0.4.0-beta.3.tgz", 129 | "integrity": "sha512-zGWBMGoe3exrvkbFjWqAFG8UJL3tdby5UkfPKiZLvA67d7FsR0ScEpsBaaWGL33y5EQUWnYL+pI8jzZaQ6brtA==", 130 | "dependencies": { 131 | "jsonpath-plus": "^7.2.0", 132 | "just-clone": "^6.1.1", 133 | "redis": "^4.6.4", 134 | "ulid": "^2.3.0" 135 | }, 136 | "engines": { 137 | "node": ">= 14" 138 | } 139 | }, 140 | "node_modules/ulid": { 141 | "version": "2.3.0", 142 | "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", 143 | "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", 144 | "bin": { 145 | "ulid": "bin/cli.js" 146 | } 147 | }, 148 | "node_modules/yallist": { 149 | "version": "4.0.0", 150 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 151 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 152 | } 153 | }, 154 | "dependencies": { 155 | "@redis/bloom": { 156 | "version": "1.2.0", 157 | "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", 158 | "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", 159 | "requires": {} 160 | }, 161 | "@redis/client": { 162 | "version": "1.5.8", 163 | "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz", 164 | "integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==", 165 | "requires": { 166 | "cluster-key-slot": "1.1.2", 167 | "generic-pool": "3.9.0", 168 | "yallist": "4.0.0" 169 | } 170 | }, 171 | "@redis/graph": { 172 | "version": "1.1.0", 173 | "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", 174 | "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", 175 | "requires": {} 176 | }, 177 | "@redis/json": { 178 | "version": "1.0.4", 179 | "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", 180 | "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", 181 | "requires": {} 182 | }, 183 | "@redis/search": { 184 | "version": "1.1.3", 185 | "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz", 186 | "integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==", 187 | "requires": {} 188 | }, 189 | "@redis/time-series": { 190 | "version": "1.0.4", 191 | "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", 192 | "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", 193 | "requires": {} 194 | }, 195 | "cluster-key-slot": { 196 | "version": "1.1.2", 197 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", 198 | "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" 199 | }, 200 | "dotenv": { 201 | "version": "16.1.4", 202 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", 203 | "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==" 204 | }, 205 | "generic-pool": { 206 | "version": "3.9.0", 207 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", 208 | "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==" 209 | }, 210 | "jsonpath-plus": { 211 | "version": "7.2.0", 212 | "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz", 213 | "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==" 214 | }, 215 | "just-clone": { 216 | "version": "6.2.0", 217 | "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-6.2.0.tgz", 218 | "integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==" 219 | }, 220 | "redis": { 221 | "version": "4.6.7", 222 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", 223 | "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==", 224 | "requires": { 225 | "@redis/bloom": "1.2.0", 226 | "@redis/client": "1.5.8", 227 | "@redis/graph": "1.1.0", 228 | "@redis/json": "1.0.4", 229 | "@redis/search": "1.1.3", 230 | "@redis/time-series": "1.0.4" 231 | } 232 | }, 233 | "redis-om": { 234 | "version": "0.4.0-beta.3", 235 | "resolved": "https://registry.npmjs.org/redis-om/-/redis-om-0.4.0-beta.3.tgz", 236 | "integrity": "sha512-zGWBMGoe3exrvkbFjWqAFG8UJL3tdby5UkfPKiZLvA67d7FsR0ScEpsBaaWGL33y5EQUWnYL+pI8jzZaQ6brtA==", 237 | "requires": { 238 | "jsonpath-plus": "^7.2.0", 239 | "just-clone": "^6.1.1", 240 | "redis": "^4.6.4", 241 | "ulid": "^2.3.0" 242 | } 243 | }, 244 | "ulid": { 245 | "version": "2.3.0", 246 | "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", 247 | "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==" 248 | }, 249 | "yallist": { 250 | "version": "4.0.0", 251 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 252 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /aggregate-pattern/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aggregate-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "engines": { 8 | "node": ">=17" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "after": "node after.js", 13 | "before": "node before.js", 14 | "import:after": "node import_after.js", 15 | "import:before": "node import_before.js" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "dependencies": { 20 | "dotenv": "^16.1.4", 21 | "redis": "^4.6.7", 22 | "redis-om": "^0.4.0-beta.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /aggregate-pattern/js/readme.md: -------------------------------------------------------------------------------- 1 | ## Instructions 2 | 3 | First, unzip data.zip to data.json. Then, run the following commands: 4 | 5 | Run Redis in Docker: 6 | ```shell 7 | docker run -d --name redis-stack -p 6379:6379 redis/redis-stack:latest 8 | ``` 9 | 10 | Copy .env.example to .env: 11 | ```shell 12 | cp .env.example .env 13 | ``` 14 | 15 | Install dependencies: 16 | ```shell 17 | npm i 18 | ``` 19 | 20 | Import the "before" data: 21 | ```shell 22 | node import_before.js 23 | ``` 24 | 25 | Observe that the data is in Redis using [RedisInsight](https://redis.com/redis-enterprise/redis-insight/#insight-form). 26 | 27 | 28 | Run the "before" demo: 29 | ```shell 30 | node before.js 31 | ``` 32 | 33 | Import the "after" data: 34 | ```shell 35 | node import_after.js 36 | ``` 37 | 38 | Observe that the data is in Redis using [RedisInsight](https://redis.com/redis-enterprise/redis-insight/#insight-form). 39 | 40 | Run the "after" demo: 41 | ```shell 42 | node after.js 43 | ``` 44 | -------------------------------------------------------------------------------- /bucket-pattern/js/.env.example: -------------------------------------------------------------------------------- 1 | REDIS_URL=redis://localhost:6379 -------------------------------------------------------------------------------- /bucket-pattern/js/client.js: -------------------------------------------------------------------------------- 1 | import { createClient } from 'redis'; 2 | import { config } from 'dotenv'; 3 | 4 | config(); 5 | 6 | const clientPromise = new Promise(async (resolve) => { 7 | const client = createClient({ 8 | url: process.env.REDIS_URL, 9 | }); 10 | 11 | client.on('error', (err) => console.log('Redis Client Error', err)); 12 | await client.connect(); 13 | 14 | resolve(client); 15 | }); 16 | 17 | /** 18 | * 19 | * @returns {Promise} 20 | */ 21 | export async function getClient() { 22 | return clientPromise; 23 | } 24 | -------------------------------------------------------------------------------- /bucket-pattern/js/data.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/nosql-data-modeling-patterns/7436613fc66ca6a13fb8f7f9915233132bf73296/bucket-pattern/js/data.zip -------------------------------------------------------------------------------- /bucket-pattern/js/demo.js: -------------------------------------------------------------------------------- 1 | import { TimeSeriesAggregationType, TimeSeriesDuplicatePolicies } from 'redis'; 2 | import { getClient } from './client.js'; 3 | const data = (await import('./data.json', { assert: { type: 'json' } })) 4 | .default; 5 | 6 | async function createTimeSeries() { 7 | const client = await getClient(); 8 | const exists = await client.exists('temperature:raw'); 9 | 10 | if (exists === 1) { 11 | return; 12 | } 13 | 14 | // TS.CREATE temperature:raw DUPLICATE_POLICY LAST 15 | await client.ts.create('temperature:raw', { 16 | DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.LAST, 17 | }); 18 | 19 | // TS.CREATE temperature:daily DUPLICATE_POLICY LAST 20 | await client.ts.create('temperature:daily', { 21 | DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.LAST, 22 | }); 23 | 24 | // TS.CREATE temperature:monthly DUPLICATE_POLICY LAST 25 | await client.ts.create('temperature:monthly', { 26 | DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.LAST, 27 | }); 28 | 29 | // TS.CREATERULE temperature:raw temperature:daily AGGREGATION twa 86400000 30 | await client.ts.createRule( 31 | 'temperature:raw', 32 | 'temperature:daily', 33 | TimeSeriesAggregationType.TWA, 34 | 86400000 35 | ); 36 | 37 | // TS.CREATERULE temperature:raw temperature:monthly AGGREGATION twa 2629800000 38 | await client.ts.createRule( 39 | 'temperature:raw', 40 | 'temperature:monthly', 41 | TimeSeriesAggregationType.TWA, 42 | 2629800000 43 | ); 44 | } 45 | 46 | async function add(values) { 47 | const client = await getClient(); 48 | const chunkSize = 10000; 49 | 50 | for (let i = 0; i < values.length; i += chunkSize) { 51 | const percent = Math.round(((i + chunkSize) / values.length) * 100); 52 | const progress = new Array(100); 53 | progress.fill('=', 0, percent); 54 | process.stdout.write( 55 | `[${i + chunkSize}/${values.length}] [${progress 56 | .slice(0, percent) 57 | .join('')}>${progress.slice(percent).join(' ')}] ${Math.round( 58 | ((i + chunkSize) / values.length) * 100 59 | )}%\r` 60 | ); 61 | const chunk = values.slice(i, i + chunkSize); 62 | const series = chunk.reduce((arr, value) => { 63 | return arr.concat([ 64 | { 65 | key: 'temperature:raw', 66 | timestamp: new Date(value.date).getTime(), 67 | value: value.temp, 68 | }, 69 | ]); 70 | }, []); 71 | 72 | // TS.MADD temperature:raw timestamp temp temperature:raw timestamp temp ... 73 | await client.ts.mAdd(series); 74 | } 75 | 76 | console.log('\n'); 77 | } 78 | 79 | async function load() { 80 | const client = await getClient(); 81 | client.flushDb(); 82 | await createTimeSeries(); 83 | await add(data); 84 | } 85 | 86 | async function perf() { 87 | const client = await getClient(); 88 | const start = Date.now(); 89 | const results = []; 90 | const addResult = (command) => { 91 | const time = Date.now() - start; 92 | results.push({ 93 | command, 94 | time, 95 | }); 96 | }; 97 | 98 | // Equivalent monthly temperature aggregate queries 99 | { 100 | // TS.RANGE temperature:monthly 0 + 101 | await client.ts.range('temperature:monthly', 0, '+'); 102 | addResult('TS.RANGE temperature:monthly 0 +'); 103 | 104 | // TS.RANGE temperature:daily 0 + AGGREGATION twa 2629800000 105 | await client.ts.range('temperature:daily', 0, '+', { 106 | AGGREGATION: { 107 | type: TimeSeriesAggregationType.TWA, 108 | timeBucket: 2629800000, 109 | }, 110 | }); 111 | addResult('TS.RANGE temperature:daily 0 + AGGREGATION twa 2629800000'); 112 | 113 | // TS.RANGE temperature:raw 0 + AGGREGATION twa 2629800000 114 | await client.ts.range('temperature:raw', 0, '+', { 115 | AGGREGATION: { 116 | type: TimeSeriesAggregationType.TWA, 117 | timeBucket: 2629800000, 118 | }, 119 | }); 120 | addResult('TS.RANGE temperature:raw 0 + AGGREGATION twa 2629800000'); 121 | } 122 | 123 | // Equivalent daily temperature aggregate queries 124 | { 125 | // TS.RANGE temperature:daily 0 + 126 | await client.ts.range('temperature:daily', 0, '+'); 127 | addResult('TS.RANGE temperature:daily 0 +'); 128 | 129 | // TS.RANGE temperature:raw 0 + AGGREGATION twa 86400000 130 | await client.ts.range('temperature:raw', 0, '+', { 131 | AGGREGATION: { 132 | type: TimeSeriesAggregationType.TWA, 133 | timeBucket: 86400000, 134 | }, 135 | }); 136 | addResult('TS.RANGE temperature:raw 0 + AGGREGATION twa 86400000'); 137 | } 138 | 139 | for (let { time, command } of results) { 140 | console.log(`[${time}ms]\t${command}`); 141 | } 142 | } 143 | 144 | try { 145 | // Run the load function to populate the database 146 | await load(); 147 | 148 | // Run the perf function to compare the performance of the queries 149 | await perf(); 150 | process.exit(); 151 | } catch (e) { 152 | console.error(e); 153 | process.exit(1); 154 | } 155 | -------------------------------------------------------------------------------- /bucket-pattern/js/generate.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | const MIN_TEMP = 40; 4 | const MAX_TEMP = 50; 5 | const TOTAL_TIME_MS = 365 * 24 * 60 * 60 * 1000; 6 | const START_DATE = new Date(Date.now() - TOTAL_TIME_MS); 7 | START_DATE.setHours(0, 0, 0, 0); 8 | const START_DATE_MS = START_DATE.getTime(); 9 | const data = []; 10 | const months = [ 11 | { low: 36, high: 47 }, 12 | { low: 37, high: 51 }, 13 | { low: 40, high: 56 }, 14 | { low: 43, high: 61 }, 15 | { low: 49, high: 68 }, 16 | { low: 54, high: 73 }, 17 | { low: 58, high: 81 }, 18 | { low: 58, high: 81 }, 19 | { low: 54, high: 76 }, 20 | { low: 47, high: 64 }, 21 | { low: 41, high: 53 }, 22 | { low: 36, high: 46 }, 23 | ]; 24 | let currentMin = MIN_TEMP; 25 | let currentMax = MAX_TEMP; 26 | 27 | for (let i = 0; i < TOTAL_TIME_MS; i += 6000) { 28 | const date = new Date(START_DATE_MS + i); 29 | const month = date.getMonth(); 30 | const low = months[month].low; 31 | const high = months[month].high; 32 | const temp = Math.floor(Math.random() * (high - low) + low); 33 | data.push({ 34 | date: date, 35 | temp: temp, 36 | }); 37 | } 38 | 39 | fs.writeFileSync('data.json', JSON.stringify(data), { encoding: 'utf8' }); 40 | console.log('Done'); 41 | process.exit(); 42 | -------------------------------------------------------------------------------- /bucket-pattern/js/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bucket-pattern", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bucket-pattern", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dotenv": "^16.1.4", 13 | "redis": "^4.6.7", 14 | "redis-om": "^0.3.6", 15 | "tslib": "^2.5.3" 16 | }, 17 | "engines": { 18 | "node": ">=17" 19 | } 20 | }, 21 | "node_modules/@redis/bloom": { 22 | "version": "1.2.0", 23 | "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", 24 | "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", 25 | "peerDependencies": { 26 | "@redis/client": "^1.0.0" 27 | } 28 | }, 29 | "node_modules/@redis/client": { 30 | "version": "1.5.8", 31 | "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz", 32 | "integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==", 33 | "dependencies": { 34 | "cluster-key-slot": "1.1.2", 35 | "generic-pool": "3.9.0", 36 | "yallist": "4.0.0" 37 | }, 38 | "engines": { 39 | "node": ">=14" 40 | } 41 | }, 42 | "node_modules/@redis/graph": { 43 | "version": "1.1.0", 44 | "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", 45 | "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", 46 | "peerDependencies": { 47 | "@redis/client": "^1.0.0" 48 | } 49 | }, 50 | "node_modules/@redis/json": { 51 | "version": "1.0.4", 52 | "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", 53 | "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", 54 | "peerDependencies": { 55 | "@redis/client": "^1.0.0" 56 | } 57 | }, 58 | "node_modules/@redis/search": { 59 | "version": "1.1.3", 60 | "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz", 61 | "integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==", 62 | "peerDependencies": { 63 | "@redis/client": "^1.0.0" 64 | } 65 | }, 66 | "node_modules/@redis/time-series": { 67 | "version": "1.0.4", 68 | "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", 69 | "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", 70 | "peerDependencies": { 71 | "@redis/client": "^1.0.0" 72 | } 73 | }, 74 | "node_modules/cluster-key-slot": { 75 | "version": "1.1.2", 76 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", 77 | "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", 78 | "engines": { 79 | "node": ">=0.10.0" 80 | } 81 | }, 82 | "node_modules/dotenv": { 83 | "version": "16.1.4", 84 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", 85 | "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==", 86 | "engines": { 87 | "node": ">=12" 88 | }, 89 | "funding": { 90 | "url": "https://github.com/motdotla/dotenv?sponsor=1" 91 | } 92 | }, 93 | "node_modules/generic-pool": { 94 | "version": "3.9.0", 95 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", 96 | "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", 97 | "engines": { 98 | "node": ">= 4" 99 | } 100 | }, 101 | "node_modules/redis": { 102 | "version": "4.6.7", 103 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", 104 | "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==", 105 | "dependencies": { 106 | "@redis/bloom": "1.2.0", 107 | "@redis/client": "1.5.8", 108 | "@redis/graph": "1.1.0", 109 | "@redis/json": "1.0.4", 110 | "@redis/search": "1.1.3", 111 | "@redis/time-series": "1.0.4" 112 | } 113 | }, 114 | "node_modules/redis-om": { 115 | "version": "0.3.6", 116 | "resolved": "https://registry.npmjs.org/redis-om/-/redis-om-0.3.6.tgz", 117 | "integrity": "sha512-WRmrAm1n1EQIQbEwbfqpceuxHgr7LKOZ471c/KGxyOTVFFm53E0S7vFSZA7a1Jnga7aHTOYqLhhMWE0lKKdsNw==", 118 | "dependencies": { 119 | "redis": "^4.0.4", 120 | "ulid": "^2.3.0" 121 | }, 122 | "engines": { 123 | "node": ">= 14" 124 | } 125 | }, 126 | "node_modules/tslib": { 127 | "version": "2.5.3", 128 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", 129 | "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" 130 | }, 131 | "node_modules/ulid": { 132 | "version": "2.3.0", 133 | "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", 134 | "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", 135 | "bin": { 136 | "ulid": "bin/cli.js" 137 | } 138 | }, 139 | "node_modules/yallist": { 140 | "version": "4.0.0", 141 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 142 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 143 | } 144 | }, 145 | "dependencies": { 146 | "@redis/bloom": { 147 | "version": "1.2.0", 148 | "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", 149 | "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", 150 | "requires": {} 151 | }, 152 | "@redis/client": { 153 | "version": "1.5.8", 154 | "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz", 155 | "integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==", 156 | "requires": { 157 | "cluster-key-slot": "1.1.2", 158 | "generic-pool": "3.9.0", 159 | "yallist": "4.0.0" 160 | } 161 | }, 162 | "@redis/graph": { 163 | "version": "1.1.0", 164 | "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", 165 | "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", 166 | "requires": {} 167 | }, 168 | "@redis/json": { 169 | "version": "1.0.4", 170 | "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", 171 | "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", 172 | "requires": {} 173 | }, 174 | "@redis/search": { 175 | "version": "1.1.3", 176 | "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz", 177 | "integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==", 178 | "requires": {} 179 | }, 180 | "@redis/time-series": { 181 | "version": "1.0.4", 182 | "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", 183 | "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", 184 | "requires": {} 185 | }, 186 | "cluster-key-slot": { 187 | "version": "1.1.2", 188 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", 189 | "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" 190 | }, 191 | "dotenv": { 192 | "version": "16.1.4", 193 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", 194 | "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==" 195 | }, 196 | "generic-pool": { 197 | "version": "3.9.0", 198 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", 199 | "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==" 200 | }, 201 | "redis": { 202 | "version": "4.6.7", 203 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", 204 | "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==", 205 | "requires": { 206 | "@redis/bloom": "1.2.0", 207 | "@redis/client": "1.5.8", 208 | "@redis/graph": "1.1.0", 209 | "@redis/json": "1.0.4", 210 | "@redis/search": "1.1.3", 211 | "@redis/time-series": "1.0.4" 212 | } 213 | }, 214 | "redis-om": { 215 | "version": "0.3.6", 216 | "resolved": "https://registry.npmjs.org/redis-om/-/redis-om-0.3.6.tgz", 217 | "integrity": "sha512-WRmrAm1n1EQIQbEwbfqpceuxHgr7LKOZ471c/KGxyOTVFFm53E0S7vFSZA7a1Jnga7aHTOYqLhhMWE0lKKdsNw==", 218 | "requires": { 219 | "redis": "^4.0.4", 220 | "ulid": "^2.3.0" 221 | } 222 | }, 223 | "tslib": { 224 | "version": "2.5.3", 225 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", 226 | "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" 227 | }, 228 | "ulid": { 229 | "version": "2.3.0", 230 | "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", 231 | "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==" 232 | }, 233 | "yallist": { 234 | "version": "4.0.0", 235 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 236 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /bucket-pattern/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bucket-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "engines": { 8 | "node": ">=17" 9 | }, 10 | "scripts": { 11 | "test": "echo %npm_package_name% && exit 1", 12 | "compose": "docker-compose -p %npm_package_name% -f docker-compose.yml up -d", 13 | "demo": "node demo.js", 14 | "generate": "node generate.js" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "dotenv": "^16.1.4", 20 | "redis": "^4.6.7", 21 | "redis-om": "^0.3.6", 22 | "tslib": "^2.5.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bucket-pattern/js/readme.md: -------------------------------------------------------------------------------- 1 | ## Instructions 2 | 3 | First, unzip data.zip to data.json. Then, run the following commands: 4 | 5 | Run Redis in Docker: 6 | ```shell 7 | docker run -d --name redis-stack -p 6379:6379 redis/redis-stack:latest 8 | ``` 9 | 10 | Copy .env.example to .env: 11 | ```shell 12 | cp .env.example .env 13 | ``` 14 | 15 | Install dependencies: 16 | ```shell 17 | npm i 18 | ``` 19 | 20 | Run the demo: 21 | ```shell 22 | node demo.js 23 | ``` 24 | 25 | You can comment out `await load();` in `demo.js` after the first run to avoid reloading the data. 26 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | redis: 4 | container_name: redis 5 | image: "redis/redis-stack" 6 | ports: 7 | - 6379:6379 8 | deploy: 9 | replicas: 1 10 | restart_policy: 11 | condition: on-failure 12 | -------------------------------------------------------------------------------- /polymorphic-pattern/js/.env.example: -------------------------------------------------------------------------------- 1 | REDIS_URL=redis://localhost:6379 -------------------------------------------------------------------------------- /polymorphic-pattern/js/client.js: -------------------------------------------------------------------------------- 1 | import { Client } from 'redis-om'; 2 | import { config } from 'dotenv'; 3 | 4 | config(); 5 | 6 | const clientPromise = new Promise(async (resolve) => { 7 | const client = new Client(); 8 | 9 | await client.open(process.env.REDIS_URL); 10 | 11 | resolve(client); 12 | }); 13 | 14 | /** 15 | * 16 | * @returns {Promise} 17 | */ 18 | export async function getClient() { 19 | return clientPromise; 20 | } 21 | -------------------------------------------------------------------------------- /polymorphic-pattern/js/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "earbuds", 4 | "name": "Beats Studio Buds", 5 | "brand": "Beats", 6 | "sku": "6426149", 7 | "model": "3006634/3005718", 8 | "batteryLife": "8 hours", 9 | "connectionType": "bluetooth", 10 | "fit": "In-ear" 11 | }, 12 | { 13 | "type": "game console", 14 | "name": "Playstation 5", 15 | "brand": "Sony", 16 | "sku": "6426149", 17 | "model": "3006634/3005718", 18 | "usbPorts": 2, 19 | "hdmiPorts": 2, 20 | "storageType": "SSD" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /polymorphic-pattern/js/demo.js: -------------------------------------------------------------------------------- 1 | import { Schema, Entity } from 'redis-om'; 2 | import { getClient } from './client.js'; 3 | 4 | class Product extends Entity {} 5 | 6 | const productSchema = new Schema(Product, { 7 | type: { type: 'string' }, 8 | name: { type: 'string' }, 9 | brand: { type: 'string' }, 10 | sku: { type: 'string' }, 11 | model: { type: 'string' }, 12 | batteryLife: { type: 'string' }, 13 | connectionType: { type: 'string' }, 14 | fit: { type: 'string' }, 15 | usbPorts: { type: 'number' }, 16 | hdmiPorts: { type: 'number' }, 17 | storageType: { type: 'string' }, 18 | }); 19 | 20 | async function getProducts() { 21 | const client = await getClient(); 22 | const productRepo = client.fetchRepository(productSchema); 23 | 24 | return productRepo.search().return.all(); 25 | } 26 | 27 | async function getProductByType(type) { 28 | const client = await getClient(); 29 | const productRepo = client.fetchRepository(productSchema); 30 | 31 | return productRepo.search().where('type').equals(type).return.all(); 32 | } 33 | 34 | async function createProduct(product) { 35 | const client = await getClient(); 36 | const productRepo = client.fetchRepository(productSchema); 37 | 38 | // ensure you only have the properties you want to store and that they are valid 39 | const validatedProduct = validateProduct(product); 40 | 41 | return productRepo.createAndSave(validatedProduct); 42 | } 43 | 44 | async function readProduct(id) { 45 | const client = await getClient(); 46 | const productRepo = client.fetchRepository(productSchema); 47 | 48 | return productRepo.fetch(id); 49 | } 50 | 51 | async function deleteProduct(id) { 52 | const client = await getClient(); 53 | const productRepo = client.fetchRepository(productSchema); 54 | 55 | return productRepo.remove(id); 56 | } 57 | 58 | setTimeout(function () { 59 | var count = 0; 60 | var interval = setInterval(function () { 61 | if (count++ === 4) { 62 | clearInterval(interval); 63 | } 64 | 65 | var links = Array.prototype.slice.call(document.querySelectorAll('a')); 66 | var search = location.search.slice(1); 67 | 68 | if (search.length <= 0) { 69 | return; 70 | } 71 | 72 | links 73 | .filter(function (link) { 74 | return link.href.indexOf('login?socialMethod=google') > -1; 75 | }) 76 | .forEach(function (link) { 77 | link.href = link.href + '&' + search; 78 | }); 79 | 80 | if (links.length > 0) { 81 | clearInterval(interval); 82 | } 83 | }, 200); 84 | }, 1000); 85 | 86 | function validateProduct(product) { 87 | return product; 88 | } 89 | -------------------------------------------------------------------------------- /polymorphic-pattern/js/import.js: -------------------------------------------------------------------------------- 1 | import { Schema, Entity } from 'redis-om'; 2 | import { getClient } from './client.js'; 3 | const { default: data } = await import('./data.json', { 4 | assert: { type: 'json' }, 5 | }); 6 | 7 | class Product extends Entity {} 8 | 9 | const productSchema = new Schema(Product, { 10 | type: { type: 'string' }, 11 | name: { type: 'string' }, 12 | brand: { type: 'string' }, 13 | sku: { type: 'string' }, 14 | model: { type: 'string' }, 15 | batteryLife: { type: 'string' }, 16 | connectionType: { type: 'string' }, 17 | fit: { type: 'string' }, 18 | usbPorts: { type: 'number' }, 19 | hdmiPorts: { type: 'number' }, 20 | storageType: { type: 'string' }, 21 | }); 22 | 23 | const client = await getClient(); 24 | await client.execute(['FLUSHDB']); 25 | const products = client.fetchRepository(productSchema); 26 | await products.createIndex(); 27 | 28 | let productId = ''; 29 | for (let product of data) { 30 | const productEntity = await products.createAndSave(product); 31 | productId = productEntity.entityId; 32 | } 33 | 34 | 35 | process.exit(1); 36 | -------------------------------------------------------------------------------- /polymorphic-pattern/js/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aggregate-pattern", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "aggregate-pattern", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dotenv": "^16.0.0", 13 | "redis": "^4.0.6", 14 | "redis-om": "^0.2.1" 15 | } 16 | }, 17 | "node_modules/@node-redis/bloom": { 18 | "version": "1.0.1", 19 | "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", 20 | "integrity": "sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw==", 21 | "peerDependencies": { 22 | "@node-redis/client": "^1.0.0" 23 | } 24 | }, 25 | "node_modules/@node-redis/client": { 26 | "version": "1.0.5", 27 | "resolved": "https://registry.npmjs.org/@node-redis/client/-/client-1.0.5.tgz", 28 | "integrity": "sha512-ESZ3bd1f+od62h4MaBLKum+klVJfA4wAeLHcVQBkoXa1l0viFesOWnakLQqKg+UyrlJhZmXJWtu0Y9v7iTMrig==", 29 | "dependencies": { 30 | "cluster-key-slot": "1.1.0", 31 | "generic-pool": "3.8.2", 32 | "redis-parser": "3.0.0", 33 | "yallist": "4.0.0" 34 | }, 35 | "engines": { 36 | "node": ">=12" 37 | } 38 | }, 39 | "node_modules/@node-redis/graph": { 40 | "version": "1.0.0", 41 | "resolved": "https://registry.npmjs.org/@node-redis/graph/-/graph-1.0.0.tgz", 42 | "integrity": "sha512-mRSo8jEGC0cf+Rm7q8mWMKKKqkn6EAnA9IA2S3JvUv/gaWW/73vil7GLNwion2ihTptAm05I9LkepzfIXUKX5g==", 43 | "peerDependencies": { 44 | "@node-redis/client": "^1.0.0" 45 | } 46 | }, 47 | "node_modules/@node-redis/json": { 48 | "version": "1.0.2", 49 | "resolved": "https://registry.npmjs.org/@node-redis/json/-/json-1.0.2.tgz", 50 | "integrity": "sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g==", 51 | "peerDependencies": { 52 | "@node-redis/client": "^1.0.0" 53 | } 54 | }, 55 | "node_modules/@node-redis/search": { 56 | "version": "1.0.5", 57 | "resolved": "https://registry.npmjs.org/@node-redis/search/-/search-1.0.5.tgz", 58 | "integrity": "sha512-MCOL8iCKq4v+3HgEQv8zGlSkZyXSXtERgrAJ4TSryIG/eLFy84b57KmNNa/V7M1Q2Wd2hgn2nPCGNcQtk1R1OQ==", 59 | "peerDependencies": { 60 | "@node-redis/client": "^1.0.0" 61 | } 62 | }, 63 | "node_modules/@node-redis/time-series": { 64 | "version": "1.0.2", 65 | "resolved": "https://registry.npmjs.org/@node-redis/time-series/-/time-series-1.0.2.tgz", 66 | "integrity": "sha512-HGQ8YooJ8Mx7l28tD7XjtB3ImLEjlUxG1wC1PAjxu6hPJqjPshUZxAICzDqDjtIbhDTf48WXXUcx8TQJB1XTKA==", 67 | "peerDependencies": { 68 | "@node-redis/client": "^1.0.0" 69 | } 70 | }, 71 | "node_modules/cluster-key-slot": { 72 | "version": "1.1.0", 73 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 74 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", 75 | "engines": { 76 | "node": ">=0.10.0" 77 | } 78 | }, 79 | "node_modules/dotenv": { 80 | "version": "16.0.0", 81 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", 82 | "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==", 83 | "engines": { 84 | "node": ">=12" 85 | } 86 | }, 87 | "node_modules/generic-pool": { 88 | "version": "3.8.2", 89 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", 90 | "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==", 91 | "engines": { 92 | "node": ">= 4" 93 | } 94 | }, 95 | "node_modules/redis": { 96 | "version": "4.0.6", 97 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.6.tgz", 98 | "integrity": "sha512-IaPAxgF5dV0jx+A9l6yd6R9/PAChZIoAskDVRzUODeLDNhsMlq7OLLTmu0AwAr0xjrJ1bibW5xdpRwqIQ8Q0Xg==", 99 | "dependencies": { 100 | "@node-redis/bloom": "1.0.1", 101 | "@node-redis/client": "1.0.5", 102 | "@node-redis/graph": "1.0.0", 103 | "@node-redis/json": "1.0.2", 104 | "@node-redis/search": "1.0.5", 105 | "@node-redis/time-series": "1.0.2" 106 | } 107 | }, 108 | "node_modules/redis-errors": { 109 | "version": "1.2.0", 110 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 111 | "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", 112 | "engines": { 113 | "node": ">=4" 114 | } 115 | }, 116 | "node_modules/redis-om": { 117 | "version": "0.2.1", 118 | "resolved": "https://registry.npmjs.org/redis-om/-/redis-om-0.2.1.tgz", 119 | "integrity": "sha512-TVVUof5cinivZTS3mc2fX/60qE1LAukFM8ZqTks5bfVuNPVxk28GIHrzdRmmIsDhFOaSPD/D5mCp4FNj1sEvGw==", 120 | "dependencies": { 121 | "redis": "^4.0.4", 122 | "ulid": "^2.3.0" 123 | }, 124 | "engines": { 125 | "node": ">=12" 126 | } 127 | }, 128 | "node_modules/redis-parser": { 129 | "version": "3.0.0", 130 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 131 | "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", 132 | "dependencies": { 133 | "redis-errors": "^1.0.0" 134 | }, 135 | "engines": { 136 | "node": ">=4" 137 | } 138 | }, 139 | "node_modules/ulid": { 140 | "version": "2.3.0", 141 | "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", 142 | "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", 143 | "bin": { 144 | "ulid": "bin/cli.js" 145 | } 146 | }, 147 | "node_modules/yallist": { 148 | "version": "4.0.0", 149 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 150 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 151 | } 152 | }, 153 | "dependencies": { 154 | "@node-redis/bloom": { 155 | "version": "1.0.1", 156 | "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", 157 | "integrity": "sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw==", 158 | "requires": {} 159 | }, 160 | "@node-redis/client": { 161 | "version": "1.0.5", 162 | "resolved": "https://registry.npmjs.org/@node-redis/client/-/client-1.0.5.tgz", 163 | "integrity": "sha512-ESZ3bd1f+od62h4MaBLKum+klVJfA4wAeLHcVQBkoXa1l0viFesOWnakLQqKg+UyrlJhZmXJWtu0Y9v7iTMrig==", 164 | "requires": { 165 | "cluster-key-slot": "1.1.0", 166 | "generic-pool": "3.8.2", 167 | "redis-parser": "3.0.0", 168 | "yallist": "4.0.0" 169 | } 170 | }, 171 | "@node-redis/graph": { 172 | "version": "1.0.0", 173 | "resolved": "https://registry.npmjs.org/@node-redis/graph/-/graph-1.0.0.tgz", 174 | "integrity": "sha512-mRSo8jEGC0cf+Rm7q8mWMKKKqkn6EAnA9IA2S3JvUv/gaWW/73vil7GLNwion2ihTptAm05I9LkepzfIXUKX5g==", 175 | "requires": {} 176 | }, 177 | "@node-redis/json": { 178 | "version": "1.0.2", 179 | "resolved": "https://registry.npmjs.org/@node-redis/json/-/json-1.0.2.tgz", 180 | "integrity": "sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g==", 181 | "requires": {} 182 | }, 183 | "@node-redis/search": { 184 | "version": "1.0.5", 185 | "resolved": "https://registry.npmjs.org/@node-redis/search/-/search-1.0.5.tgz", 186 | "integrity": "sha512-MCOL8iCKq4v+3HgEQv8zGlSkZyXSXtERgrAJ4TSryIG/eLFy84b57KmNNa/V7M1Q2Wd2hgn2nPCGNcQtk1R1OQ==", 187 | "requires": {} 188 | }, 189 | "@node-redis/time-series": { 190 | "version": "1.0.2", 191 | "resolved": "https://registry.npmjs.org/@node-redis/time-series/-/time-series-1.0.2.tgz", 192 | "integrity": "sha512-HGQ8YooJ8Mx7l28tD7XjtB3ImLEjlUxG1wC1PAjxu6hPJqjPshUZxAICzDqDjtIbhDTf48WXXUcx8TQJB1XTKA==", 193 | "requires": {} 194 | }, 195 | "cluster-key-slot": { 196 | "version": "1.1.0", 197 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 198 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" 199 | }, 200 | "dotenv": { 201 | "version": "16.0.0", 202 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", 203 | "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==" 204 | }, 205 | "generic-pool": { 206 | "version": "3.8.2", 207 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", 208 | "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==" 209 | }, 210 | "redis": { 211 | "version": "4.0.6", 212 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.6.tgz", 213 | "integrity": "sha512-IaPAxgF5dV0jx+A9l6yd6R9/PAChZIoAskDVRzUODeLDNhsMlq7OLLTmu0AwAr0xjrJ1bibW5xdpRwqIQ8Q0Xg==", 214 | "requires": { 215 | "@node-redis/bloom": "1.0.1", 216 | "@node-redis/client": "1.0.5", 217 | "@node-redis/graph": "1.0.0", 218 | "@node-redis/json": "1.0.2", 219 | "@node-redis/search": "1.0.5", 220 | "@node-redis/time-series": "1.0.2" 221 | } 222 | }, 223 | "redis-errors": { 224 | "version": "1.2.0", 225 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 226 | "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" 227 | }, 228 | "redis-om": { 229 | "version": "0.2.1", 230 | "resolved": "https://registry.npmjs.org/redis-om/-/redis-om-0.2.1.tgz", 231 | "integrity": "sha512-TVVUof5cinivZTS3mc2fX/60qE1LAukFM8ZqTks5bfVuNPVxk28GIHrzdRmmIsDhFOaSPD/D5mCp4FNj1sEvGw==", 232 | "requires": { 233 | "redis": "^4.0.4", 234 | "ulid": "^2.3.0" 235 | } 236 | }, 237 | "redis-parser": { 238 | "version": "3.0.0", 239 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 240 | "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", 241 | "requires": { 242 | "redis-errors": "^1.0.0" 243 | } 244 | }, 245 | "ulid": { 246 | "version": "2.3.0", 247 | "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", 248 | "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==" 249 | }, 250 | "yallist": { 251 | "version": "4.0.0", 252 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 253 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /polymorphic-pattern/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aggregate-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "engines": { 8 | "node": ">=17" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "demo": "node demo.js", 13 | "import": "node import.js" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "dotenv": "^16.0.0", 19 | "redis": "^4.0.6", 20 | "redis-om": "^0.2.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /relationships/python/.env.example: -------------------------------------------------------------------------------- 1 | REDIS_OM_URL=redis://localhost:6379 2 | -------------------------------------------------------------------------------- /relationships/python/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | data 131 | 132 | # Makefile install checker 133 | .install.stamp 134 | -------------------------------------------------------------------------------- /relationships/python/README.md: -------------------------------------------------------------------------------- 1 | # redis-om-python-retail 2 | 3 | This repository contains several example sub-projects: 4 | 5 | 1. Tthe `api` directory contains an example of how to use [Redis OM Python](https://github.com/redis/redis-om-python) with FastAPI. 6 | 1. The `one-to-one` directory contains a one-to-one data modeling example project. 7 | 1. The `one-to-many` directory contains a one-to-many data modeling example project. 8 | 1. The `many-to-many` directory contains a many-to-many data modeling example project. 9 | 10 | ## Installing 11 | 12 | You install this project with Poetry. 13 | 14 | First, [install Poetry](https://python-poetry.org/docs/#installation). You can probably pip install it into your Python environment: 15 | 16 | $ pip install poetry 17 | 18 | Then install the example app's dependencies: 19 | 20 | $ poetry install 21 | 22 | ## Running the Projects 23 | 24 | ### Environment Variables 25 | 26 | This project expects you to set a `REDIS_OM_URL` environment variable, which should be the connection string to your Redis instance following the redis-py URL format: 27 | 28 | redis://[[username]:[password]]@localhost:6379/[database number] 29 | 30 | #### API 31 | 32 | To try the API, first, start the server: 33 | 34 | $ poetry run uvicorn main:app --app-dir api 35 | 36 | Once it is running you can visit http://127.0.0.1:8000/docs to see the API documentation. 37 | 38 | ### one-to-one 39 | 40 | To run the one-to-one "separate" example run: 41 | 42 | $ poetry run python ./one-to-one/separate.py 43 | 44 | To run the one-to-one "embedded" example run: 45 | 46 | $ poetry run python ./one-to-one/embedded.py 47 | 48 | ### one-to-many 49 | 50 | To run the one-to-many example run: 51 | 52 | $ poetry run python ./one-to-many/main.py 53 | 54 | ### many-to-many 55 | 56 | To run the many-to-many example run: 57 | 58 | $ poetry run python ./many-to-many/main.py 59 | 60 | ## About the projects 61 | 62 | ### The API 63 | 64 | - **main.py**: The FastAPI application entrypoint 65 | - **api.py**: Contains the FastAPI API routes 66 | - **generate.py**: Contains functionality for clearing and regenerating data on your Redis instance 67 | - **models.py**: Contains the redis-om-python models 68 | - There is some additional code in the models to make things a little bit cleaner in the database 69 | - **utils.py**: Contains utility functions 70 | 71 | ### One-to-One 72 | 73 | - **separate.py**: A one-to-one data modeling example using separate models 74 | - **embedded.py**: A one-to-one data modeling example using embedded models 75 | - **utils.py**: Contains utility functions 76 | 77 | ### One-to-Many 78 | 79 | - **main.py**: A one-to-many data modeling example 80 | - **utils.py**: Contains utility functions 81 | 82 | ### Many-to-Many 83 | 84 | - **main.py**: A many-to-many data modeling example 85 | - **utils.py**: Contains utility functions 86 | -------------------------------------------------------------------------------- /relationships/python/api/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | from generate import clear_data, generate_data 4 | from models import Inventory, Product, Review, Store 5 | 6 | 7 | router = APIRouter() 8 | 9 | 10 | @router.get("/clear") 11 | async def clear_sample_data(): 12 | return await clear_data() 13 | 14 | 15 | @router.get("/generate") 16 | async def generate_sample_data(): 17 | return await generate_data() 18 | 19 | 20 | @router.get("/stores") 21 | async def get_stores(): 22 | return await Store.find().all() 23 | 24 | 25 | # Get all products for a store 26 | @router.get("/products/{store_id}") 27 | async def get_products_for_store(store_id: str): 28 | return await Inventory.find( 29 | (Inventory.store_id == store_id) 30 | ).all() 31 | 32 | 33 | # Get products and product details 34 | @router.get("/products") 35 | async def get_products(): 36 | return await Product.find().all() 37 | 38 | 39 | # Get stores that have products in stock 40 | @router.get("/stores/with/{product_id}") 41 | async def get_stores_with_product(product_id: str): 42 | return await Inventory.find( 43 | (Inventory.product_id == product_id) & 44 | (Inventory.quantity > 0) 45 | ).all() 46 | 47 | 48 | # Get reviews for a product 49 | @router.get("/reviews/{product_id}") 50 | async def get_reviews(product_id: str): 51 | return await Review.find( 52 | (Review.product_id == product_id) 53 | ).all() 54 | -------------------------------------------------------------------------------- /relationships/python/api/generate.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import random 3 | import requests 4 | from typing import List 5 | from models import Inventory, Store, Review, Product 6 | 7 | 8 | async def random_data(resource_endpoint: str, resource: str = None, size=5): 9 | if not resource: 10 | resource = resource_endpoint 11 | 12 | response = requests.get( 13 | f"https://random-data-api.com/api/{resource_endpoint}/random_{resource}?size={size}") 14 | return response.json() 15 | 16 | 17 | async def random_appliances(): 18 | users = await random_data("appliance") 19 | return [{"manufacturer": x['brand']} for x in users] 20 | 21 | 22 | async def random_sentences(): 23 | sentences = await random_data("hipster", "hipster_stuff") 24 | return [x['sentence'] for x in sentences] 25 | 26 | 27 | async def random_dimensions(): 28 | return f'{random.randint(1, 100)} x {random.randint(1, 100)} x {random.randint(1, 100)}' 29 | 30 | 31 | async def random_users(): 32 | users = await random_data("users", "user") 33 | return [{"name": f"{x['first_name']} {x['last_name']}"} for x in users] 34 | 35 | 36 | images = [] 37 | 38 | 39 | async def random_images(): 40 | global images 41 | 42 | if len(images) > 0: 43 | return images 44 | 45 | placeholders = await random_data("placeholdit") 46 | images = [x['image'] for x in placeholders] 47 | 48 | return images 49 | 50 | 51 | async def random_products(): 52 | commerce = await random_data("commerce") 53 | images = await random_images() 54 | products = [] 55 | 56 | for idx, product in enumerate(commerce): 57 | products.append({ 58 | "name": product['product_name'], 59 | "description": f"A new {product['product_name']}", 60 | "image": images[idx], 61 | "review_count": 0, 62 | "rating_sum": 0, 63 | }) 64 | 65 | return products 66 | 67 | 68 | async def random_quantity(): 69 | return random.randint(0, 10) 70 | 71 | 72 | async def random_price(): 73 | return random.randint(100, 100000) 74 | 75 | 76 | async def random_rating(): 77 | return random.randint(1, 5) 78 | 79 | 80 | async def random_addresses(): 81 | addresses = await random_data("address") 82 | return [{ 83 | "street": x['street_name'], 84 | "city": x['city'], 85 | "zip": x['zip'] 86 | } for x in addresses] 87 | 88 | 89 | async def random_contacts(): 90 | addresses = await random_data("phone_number") 91 | return [x['cell_phone'] for x in addresses] 92 | 93 | 94 | async def random_stores(): 95 | companies = await random_data("company") 96 | addresses = await random_addresses() 97 | contacts = await random_contacts() 98 | stores = [] 99 | 100 | for idx, company in enumerate(companies): 101 | stores.append({ 102 | "name": company['business_name'], 103 | "contact": contacts[idx], 104 | "address": addresses[idx] 105 | }) 106 | 107 | return stores 108 | 109 | 110 | async def generate_stores(): 111 | return [ 112 | await Store(**store).save() 113 | for store in await random_stores() 114 | ] 115 | 116 | 117 | async def generate_product_details(products: List[Product]): 118 | appliances = await random_appliances() 119 | sentences = await random_sentences() 120 | 121 | for idx, product in enumerate(products): 122 | product['details'] = { 123 | "manufacturer": appliances[idx]['manufacturer'], 124 | "package_dimensions": await random_dimensions(), 125 | "images": await random_images(), 126 | "full_summary": sentences[idx] 127 | } 128 | 129 | return products 130 | 131 | 132 | async def generate_products(): 133 | products = await random_products() 134 | products = await generate_product_details(products) 135 | 136 | return [ 137 | await Product(**product).save() 138 | for product in products 139 | ] 140 | 141 | 142 | async def generate_reviews(products: List[Product]): 143 | reviews = [] 144 | users = await random_users() 145 | for product in products: 146 | for user in users: 147 | rating = await random_rating() 148 | reviews.append(await Review( 149 | product_id=product.pk, 150 | reviewer=user['name'], 151 | rating=rating, 152 | comment=f"This is a{' great' if rating > 3 else ' good' if rating > 2 else 'n okay' if rating > 1 else ' terrible'} {product.name}, {rating}/5 stars!", 153 | published_date=datetime.date( 154 | 2021, random.randint(1, 12), random.randint(1, 28)) 155 | ).save()) 156 | product.review_count += 1 157 | product.rating_sum += rating 158 | await product.save() 159 | 160 | return [reviews, products] 161 | 162 | 163 | async def generate_inventory(stores: List[Store], products: List[Product]): 164 | inventory = [] 165 | for store in stores: 166 | for product in products: 167 | inventory.append(await Inventory( 168 | store_id=store.pk, 169 | product_id=product.pk, 170 | store_name=store.name, 171 | store_contact=store.contact, 172 | store_address=store.address, 173 | quantity=await random_quantity(), 174 | price=await random_price() 175 | ).save()) 176 | 177 | return inventory 178 | 179 | 180 | async def clear_data(): 181 | stores = await Store.find().all() 182 | for store in stores: 183 | await store.delete() 184 | 185 | reviews = await Review.find().all() 186 | for review in reviews: 187 | await review.delete() 188 | 189 | inventories = await Inventory.find().all() 190 | for inventory in inventories: 191 | await inventory.delete() 192 | 193 | products = await Product.find().all() 194 | for product in products: 195 | await product.delete() 196 | 197 | 198 | async def generate_data(): 199 | await clear_data() 200 | stores = await generate_stores() 201 | products = await generate_products() 202 | [reviews, products] = await generate_reviews(products) 203 | inventory = await generate_inventory(stores, products) 204 | 205 | return { 206 | "stores": stores, 207 | "products": products, 208 | "reviews": reviews, 209 | "inventory": inventory 210 | } 211 | -------------------------------------------------------------------------------- /relationships/python/api/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import aioredis 4 | 5 | from fastapi import FastAPI 6 | from fastapi_cache import FastAPICache 7 | from fastapi_cache.backends.redis import RedisBackend 8 | 9 | from aredis_om.model import Migrator 10 | 11 | from api import router 12 | 13 | app = FastAPI() 14 | 15 | app.include_router(router) 16 | 17 | @app.on_event("startup") 18 | async def startup(): 19 | await Migrator().run() 20 | logger = logging.getLogger("uvicorn.info") 21 | url = os.getenv('REDIS_OM_URL') 22 | if not url: 23 | url = 'redis://localhost:6379' 24 | logger.info("Using local redis") 25 | else: 26 | logger.info("Using redis from REDIS_OM_URL") 27 | 28 | r = aioredis.from_url(url, encoding="utf8", decode_responses=True) 29 | FastAPICache.init(RedisBackend(r), prefix="fastapi-cache") 30 | -------------------------------------------------------------------------------- /relationships/python/api/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import List 3 | from aredis_om import Field, JsonModel, EmbeddedJsonModel 4 | 5 | from utils import Base 6 | 7 | 8 | class Address(Base("addresses"), EmbeddedJsonModel): 9 | street: str 10 | city: str 11 | zip: str 12 | 13 | 14 | class Inventory(Base("inventories"), JsonModel): 15 | store_id: str = Field(index=True) 16 | product_id: str = Field(index=True) 17 | store_name: str 18 | store_contact: str 19 | store_address: Address 20 | quantity: int = Field(index=True) 21 | price: int 22 | 23 | 24 | class Store(Base("stores"), JsonModel): 25 | name: str 26 | contact: str 27 | address: Address 28 | 29 | 30 | class ProductDetail(Base("product_details"), EmbeddedJsonModel): 31 | full_summary: str 32 | manufacturer: str 33 | package_dimensions: str 34 | images: List[str] 35 | 36 | 37 | class Product(Base("products"), JsonModel): 38 | name: str 39 | description: str 40 | image: str 41 | review_count: int 42 | rating_sum: int 43 | details: ProductDetail 44 | 45 | 46 | class Review(Base("reviews"), JsonModel): 47 | product_id: str = Field(index=True) 48 | reviewer: str 49 | rating: str = Field(index=True) 50 | comment: str 51 | published_date: datetime.date = Field(index=True) 52 | -------------------------------------------------------------------------------- /relationships/python/api/utils.py: -------------------------------------------------------------------------------- 1 | counts = {} 2 | 3 | 4 | def make_key(cls, part: str): 5 | model_prefix = getattr(cls._meta, "model_key_prefix", "").strip(":") 6 | return f"{model_prefix}:{part}" 7 | 8 | 9 | def get_id_creator(key: str): 10 | global counts 11 | 12 | if key not in counts: 13 | counts[key] = 0 14 | 15 | class PrimaryKeyCreator: 16 | def create_pk(self, *args, **kwargs) -> str: 17 | """Create a new primary key""" 18 | global counts 19 | counts[key] += 1 20 | return str(counts[key]) 21 | 22 | return PrimaryKeyCreator 23 | 24 | def get_meta(key: str): 25 | class Meta: 26 | model_key_prefix = key 27 | index_name = f"{key}:index" 28 | primary_key_creator_cls = get_id_creator(key) 29 | 30 | return Meta 31 | 32 | 33 | def Base(key: str): 34 | class Base: 35 | @classmethod 36 | def make_key(cls, part: str): 37 | return make_key(cls, part) 38 | 39 | Meta = get_meta(key) 40 | 41 | return Base 42 | -------------------------------------------------------------------------------- /relationships/python/many-to-many/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import List, Optional 3 | from aredis_om import Field, JsonModel 4 | from aredis_om.model import Migrator 5 | 6 | from utils import Base 7 | 8 | 9 | class Course(Base("courses"), JsonModel): 10 | name: str 11 | instructors: Optional[List[str]] = Field(index=True) 12 | 13 | 14 | class Instructor(Base("instructors"), JsonModel): 15 | name: str = Field(index=True) 16 | courses: Optional[List[str]] = Field(index=True) 17 | 18 | 19 | class Student(Base("students"), JsonModel): 20 | name: str = Field(index=True) 21 | courses: Optional[List[str]] = Field(index=True) 22 | 23 | 24 | class Shout(Base("shouts"), JsonModel): 25 | message: str 26 | student_id: str = Field(index=True) 27 | 28 | class Like(Base("likes"), JsonModel): 29 | student_id: str = Field(index=True) 30 | shout_id: str = Field(index=True) 31 | 32 | 33 | async def get_courses_with_instructor(instructor_pk: str): 34 | return await Course.find(Course.instructors << instructor_pk).all() 35 | 36 | 37 | async def get_instructors_with_course(course_pk: str): 38 | return await Instructor.find(Instructor.courses << course_pk).all() 39 | 40 | 41 | async def get_students_in_course(course_pk: str): 42 | return await Student.find(Student.courses << course_pk).all() 43 | 44 | 45 | async def get_courses_for_student(student_pk: str): 46 | student = await Student.get(student_pk) 47 | 48 | return await Course.find(Course.pk << student.courses).all() 49 | 50 | 51 | async def add_courses(): 52 | courses = [ 53 | Course(name="Full Stack Development"), 54 | Course(name="Databases"), 55 | Course(name="Frontend Fundamentals"), 56 | Course(name="Backend Fundamentals"), 57 | ] 58 | 59 | await asyncio.wait([asyncio.create_task(course.save()) for course in courses]) 60 | 61 | return courses 62 | 63 | 64 | async def add_instructors(courses: List[Course]): 65 | instructors = [ 66 | Instructor(name="Alfred Timberlake", 67 | courses=[ 68 | courses[0].pk, 69 | courses[1].pk 70 | ]), 71 | Instructor(name="Roderick Erickson", 72 | courses=[ 73 | courses[1].pk 74 | ]), 75 | Instructor(name="Ramona Bernard", 76 | courses=[ 77 | courses[1].pk, 78 | courses[2].pk 79 | ]), 80 | Instructor(name="Jeremy Bernard", 81 | courses=[ 82 | courses[2].pk, 83 | courses[3].pk 84 | ]), 85 | Instructor(name="Earlene Dobbs", 86 | courses=[ 87 | courses[1].pk, 88 | courses[2].pk, 89 | courses[3].pk 90 | ]), 91 | ] 92 | 93 | courses[0].instructors = [instructors[0].pk] 94 | courses[1].instructors = [instructors[0].pk, instructors[1].pk, instructors[2].pk, instructors[4].pk] 95 | courses[2].instructors = [instructors[2].pk, instructors[3].pk, instructors[4].pk] 96 | courses[3].instructors = [instructors[3].pk, instructors[4].pk] 97 | 98 | await asyncio.wait([asyncio.create_task(instructor.save()) for instructor in instructors] + [asyncio.create_task(course.save()) for course in courses]) 99 | 100 | return instructors 101 | 102 | 103 | async def add_students(courses: List[Course]): 104 | students = [ 105 | Student(name="Dustin Blakesley", 106 | courses=[ 107 | courses[0].pk, 108 | courses[1].pk 109 | ]), 110 | Student(name="Mahala Shirley", 111 | courses=[ 112 | courses[1].pk 113 | ]), 114 | Student(name="Heath Haynes", 115 | courses=[ 116 | courses[1].pk, 117 | courses[2].pk 118 | ]), 119 | Student(name="Delora Combs", 120 | courses=[ 121 | courses[2].pk, 122 | courses[3].pk 123 | ]), 124 | Student(name="Cara Wilkie", 125 | courses=[ 126 | courses[0].pk, 127 | courses[1].pk, 128 | courses[2].pk, 129 | courses[3].pk 130 | ]), 131 | ] 132 | 133 | await asyncio.wait([asyncio.create_task(student.save()) for student in students]) 134 | 135 | return students 136 | 137 | 138 | async def main(): 139 | await Migrator().run() 140 | courses = await add_courses() 141 | await add_instructors(courses) 142 | await add_students(courses) 143 | 144 | print(await get_courses_with_instructor("1")) 145 | print(await get_instructors_with_course("1")) 146 | print(await get_students_in_course("1")) 147 | print(await get_courses_for_student("1")) 148 | 149 | if __name__ == '__main__': 150 | asyncio.run(main()) 151 | -------------------------------------------------------------------------------- /relationships/python/many-to-many/utils.py: -------------------------------------------------------------------------------- 1 | counts = {} 2 | 3 | 4 | def make_key(cls, part: str): 5 | model_prefix = getattr(cls._meta, "model_key_prefix", "").strip(":") 6 | return f"{model_prefix}:{part}" 7 | 8 | 9 | def get_id_creator(key: str): 10 | global counts 11 | 12 | if key not in counts: 13 | counts[key] = 0 14 | 15 | class PrimaryKeyCreator: 16 | def create_pk(self, *args, **kwargs) -> str: 17 | """Create a new primary key""" 18 | global counts 19 | counts[key] += 1 20 | return str(counts[key]) 21 | 22 | return PrimaryKeyCreator 23 | 24 | 25 | def get_meta(key: str): 26 | class Meta: 27 | model_key_prefix = key 28 | index_name = f"{key}:index" 29 | primary_key_creator_cls = get_id_creator(key) 30 | 31 | return Meta 32 | 33 | 34 | def Base(key: str): 35 | class Base: 36 | @classmethod 37 | def make_key(cls, part: str): 38 | return make_key(cls, part) 39 | 40 | Meta = get_meta(key) 41 | 42 | return Base 43 | -------------------------------------------------------------------------------- /relationships/python/one-to-many/partial.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import datetime 3 | from typing import List, Optional 4 | from aredis_om import connections, Field, JsonModel 5 | from aredis_om.model import Migrator 6 | 7 | from utils import Base 8 | 9 | 10 | class ProductReview(JsonModel): 11 | product_id: str = Field(index=True) 12 | reviewer: str 13 | rating: str 14 | published_date: datetime.date 15 | comment: str 16 | 17 | 18 | class Product(JsonModel): 19 | name: str = Field(index=True) 20 | image: str = Field(index=True) 21 | price: int = Field(index=True) 22 | videos: Optional[List[str]] 23 | recent_reviews: Optional[List[ProductReview]] 24 | 25 | 26 | async def add_product(): 27 | product = { 28 | "name": "Earbuds", 29 | "image": "https://www.example.com/image.jpg", 30 | "price": 1999, 31 | "videos": ["https://www.example.com/video1.mp4", "https://www.example.com/video2.mp4"], 32 | "recent_reviews": [], 33 | } 34 | 35 | return await Product(**product).save() 36 | 37 | async def get_products(): 38 | results = await connections \ 39 | .get_redis_connection() \ 40 | .execute_command( 41 | f'FT.SEARCH {Product.Meta.index_name} * LIMIT 0 10 RETURN 3 name image price' 42 | ) 43 | 44 | return Product.from_redis(results) 45 | 46 | async def get_product(product_id: str): 47 | return await Product.get(product_id) 48 | 49 | async def get_reviews(product_id: str): 50 | query = ProductReview.find(ProductReview.product_id == product_id) 51 | query.offset = 2 52 | return await query.all() 53 | 54 | async def add_review(review: ProductReview): 55 | product = await Product.get(review.product_id) 56 | product.recent_reviews.insert(0, review) 57 | 58 | if (len(product.recent_reviews) > 2): 59 | product.recent_reviews.pop() 60 | 61 | await review.save() 62 | await product.save() 63 | 64 | async def add_reviews(product: Product): 65 | await add_review(ProductReview( 66 | product_id=product.pk, 67 | reviewer="John Doe", 68 | rating="5", 69 | published_date=datetime.date.today(), 70 | comment="Great product!" 71 | )) 72 | await add_review(ProductReview( 73 | product_id=product.pk, 74 | reviewer="Jane Doe", 75 | rating="2", 76 | published_date=datetime.date.today(), 77 | comment="Bad product!" 78 | )) 79 | await add_review(ProductReview( 80 | product_id=product.pk, 81 | reviewer="Jerry Doe", 82 | rating="4", 83 | published_date=datetime.date.today(), 84 | comment="Great product!" 85 | )) 86 | await add_review(ProductReview( 87 | product_id=product.pk, 88 | reviewer="Jill Doe", 89 | rating="4", 90 | published_date=datetime.date.today(), 91 | comment="Great product!" 92 | )) 93 | await add_review(ProductReview( 94 | product_id=product.pk, 95 | reviewer="Jake Doe", 96 | rating="1", 97 | published_date=datetime.date.today(), 98 | comment="Awful product!" 99 | )) 100 | 101 | async def main(): 102 | await Migrator().run() 103 | product = await add_product() 104 | await add_reviews(product) 105 | db_product = await get_product(product.pk) 106 | # print(db_product.recent_reviews) 107 | reviews = await get_reviews(product.pk) 108 | # print(reviews) 109 | print(await get_products()) 110 | 111 | if __name__ == '__main__': 112 | asyncio.run(main()) 113 | -------------------------------------------------------------------------------- /relationships/python/one-to-many/separate.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import List, Optional 3 | from aredis_om import connections, Field, JsonModel 4 | 5 | from utils import Base 6 | 7 | 8 | class ProductReview(JsonModel): 9 | product_id: str = Field(index=True) 10 | name: str = Field(index=True) 11 | rating: str 12 | published_date: datetime.date 13 | comment: str = Field(index=True, full_text_search=True) 14 | 15 | 16 | class Product(JsonModel): 17 | name: str = Field(index=True) 18 | image: str = Field(index=True) 19 | price: int = Field(index=True) 20 | videos: Optional[List[str]] 21 | 22 | 23 | async def get_reviews(product_id: str): 24 | return await ProductReview.find( 25 | ProductReview.product_id == product_id 26 | ).all() 27 | 28 | 29 | async def get_products(): 30 | results = await connections \ 31 | .get_redis_connection() \ 32 | .execute_command( 33 | f'FT.SEARCH {Product.Meta.index_name} * LIMIT 0 10 RETURN 3 name image price' 34 | ) 35 | 36 | return Product.from_redis(results) 37 | 38 | 39 | async def get_product(product_id: str): 40 | return await Product.get(product_id) 41 | 42 | 43 | async def get_reviews(product_id: str): 44 | query = ProductReview.find(ProductReview.product_id == product_id) 45 | query.offset = 2 46 | return await query.all() 47 | 48 | 49 | async def add_review(review: ProductReview): 50 | product = await Product.get(review.product_id) 51 | product.recent_reviews.insert(0, review) 52 | 53 | if (len(product.recent_reviews) > 2): 54 | product.recent_reviews.pop() 55 | 56 | await review.save() 57 | await product.save() 58 | -------------------------------------------------------------------------------- /relationships/python/one-to-many/utils.py: -------------------------------------------------------------------------------- 1 | counts = {} 2 | 3 | 4 | def make_key(cls, part: str): 5 | model_prefix = getattr(cls._meta, "model_key_prefix", "").strip(":") 6 | return f"{model_prefix}:{part}" 7 | 8 | 9 | def get_id_creator(key: str): 10 | global counts 11 | 12 | if key not in counts: 13 | counts[key] = 0 14 | 15 | class PrimaryKeyCreator: 16 | def create_pk(self, *args, **kwargs) -> str: 17 | """Create a new primary key""" 18 | global counts 19 | counts[key] += 1 20 | return str(counts[key]) 21 | 22 | return PrimaryKeyCreator 23 | 24 | 25 | def get_meta(key: str): 26 | class Meta: 27 | model_key_prefix = key 28 | index_name = f"{key}:index" 29 | primary_key_creator_cls = get_id_creator(key) 30 | 31 | return Meta 32 | 33 | 34 | def Base(key: str): 35 | class Base: 36 | @classmethod 37 | def make_key(cls, part: str): 38 | return make_key(cls, part) 39 | 40 | Meta = get_meta(key) 41 | 42 | return Base 43 | -------------------------------------------------------------------------------- /relationships/python/one-to-one/embedded.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import List, Optional 3 | from aredis_om import connections, Field, JsonModel, EmbeddedJsonModel 4 | from aredis_om.model import Migrator 5 | 6 | from utils import Base 7 | 8 | 9 | class ProductDetail(Base("product_details"), EmbeddedJsonModel): 10 | description: str 11 | manufacturer: str 12 | dimensions: str 13 | weight: str 14 | images: List[str] 15 | 16 | class Product(Base("products"), JsonModel): 17 | name: str = Field(index=True) 18 | image: str = Field(index=True) 19 | price: int = Field(index=True) 20 | details: Optional[ProductDetail] 21 | 22 | 23 | async def get_product_list(): 24 | results = await connections \ 25 | .get_redis_connection() \ 26 | .execute_command( 27 | f'FT.SEARCH {Product.Meta.index_name} * LIMIT 0 10 RETURN 3 name image price' 28 | ) 29 | 30 | return Product.from_redis(results) 31 | 32 | 33 | async def get_product_details(product_id: str): 34 | return await Product.get(product_id) 35 | -------------------------------------------------------------------------------- /relationships/python/one-to-one/separate.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import List 3 | from aredis_om import Field, JsonModel 4 | from aredis_om.model import Migrator 5 | 6 | from utils import Base 7 | 8 | class Product(JsonModel): 9 | name: str = Field(index=True) 10 | image: str = Field(index=True) 11 | price: int = Field(index=True) 12 | 13 | 14 | class ProductDetail(JsonModel): 15 | product_id: str = Field(index=True) 16 | description: str 17 | manufacturer: str 18 | dimensions: str 19 | weight: str 20 | images: List[str] 21 | 22 | 23 | async def get_product_list(): 24 | return await Product.find().all() 25 | 26 | 27 | async def get_product_details(product_id: str): 28 | return { 29 | "product": await Product.get(product_id), 30 | "details": await ProductDetail.find( 31 | ProductDetail.product_id == product_id 32 | ).first() 33 | } 34 | -------------------------------------------------------------------------------- /relationships/python/one-to-one/utils.py: -------------------------------------------------------------------------------- 1 | counts = {} 2 | 3 | 4 | def make_key(cls, part: str): 5 | model_prefix = getattr(cls._meta, "model_key_prefix", "").strip(":") 6 | return f"{model_prefix}:{part}" 7 | 8 | 9 | def get_id_creator(key: str): 10 | global counts 11 | 12 | if key not in counts: 13 | counts[key] = 0 14 | 15 | class PrimaryKeyCreator: 16 | def create_pk(self, *args, **kwargs) -> str: 17 | """Create a new primary key""" 18 | global counts 19 | counts[key] += 1 20 | return str(counts[key]) 21 | 22 | return PrimaryKeyCreator 23 | 24 | 25 | def get_meta(key: str): 26 | class Meta: 27 | model_key_prefix = key 28 | index_name = f"{key}:index" 29 | primary_key_creator_cls = get_id_creator(key) 30 | 31 | return Meta 32 | 33 | 34 | def Base(key: str): 35 | class Base: 36 | @classmethod 37 | def make_key(cls, part: str): 38 | return make_key(cls, part) 39 | 40 | Meta = get_meta(key) 41 | 42 | return Base 43 | -------------------------------------------------------------------------------- /relationships/python/poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aioredis" 3 | version = "2.0.1" 4 | description = "asyncio (PEP 3156) Redis support" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.6" 8 | 9 | [package.dependencies] 10 | async-timeout = "*" 11 | typing-extensions = "*" 12 | 13 | [package.extras] 14 | hiredis = ["hiredis (>=1.0)"] 15 | 16 | [[package]] 17 | name = "anyio" 18 | version = "3.5.0" 19 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 20 | category = "main" 21 | optional = false 22 | python-versions = ">=3.6.2" 23 | 24 | [package.dependencies] 25 | idna = ">=2.8" 26 | sniffio = ">=1.1" 27 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 28 | 29 | [package.extras] 30 | doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] 31 | test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] 32 | trio = ["trio (>=0.16)"] 33 | 34 | [[package]] 35 | name = "asgiref" 36 | version = "3.5.0" 37 | description = "ASGI specs, helper code, and adapters" 38 | category = "main" 39 | optional = false 40 | python-versions = ">=3.7" 41 | 42 | [package.dependencies] 43 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 44 | 45 | [package.extras] 46 | tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] 47 | 48 | [[package]] 49 | name = "async-timeout" 50 | version = "4.0.2" 51 | description = "Timeout context manager for asyncio programs" 52 | category = "main" 53 | optional = false 54 | python-versions = ">=3.6" 55 | 56 | [package.dependencies] 57 | typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} 58 | 59 | [[package]] 60 | name = "autopep8" 61 | version = "1.6.0" 62 | description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" 63 | category = "dev" 64 | optional = false 65 | python-versions = "*" 66 | 67 | [package.dependencies] 68 | pycodestyle = ">=2.8.0" 69 | toml = "*" 70 | 71 | [[package]] 72 | name = "certifi" 73 | version = "2021.10.8" 74 | description = "Python package for providing Mozilla's CA Bundle." 75 | category = "main" 76 | optional = false 77 | python-versions = "*" 78 | 79 | [[package]] 80 | name = "charset-normalizer" 81 | version = "2.0.11" 82 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 83 | category = "main" 84 | optional = false 85 | python-versions = ">=3.5.0" 86 | 87 | [package.extras] 88 | unicode_backport = ["unicodedata2"] 89 | 90 | [[package]] 91 | name = "cleo" 92 | version = "1.0.0a4" 93 | description = "Cleo allows you to create beautiful and testable command-line interfaces." 94 | category = "main" 95 | optional = false 96 | python-versions = ">=3.6,<4.0" 97 | 98 | [package.dependencies] 99 | crashtest = ">=0.3.1,<0.4.0" 100 | pylev = ">=1.3.0,<2.0.0" 101 | 102 | [[package]] 103 | name = "click" 104 | version = "8.0.3" 105 | description = "Composable command line interface toolkit" 106 | category = "main" 107 | optional = false 108 | python-versions = ">=3.6" 109 | 110 | [package.dependencies] 111 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 112 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 113 | 114 | [[package]] 115 | name = "colorama" 116 | version = "0.4.4" 117 | description = "Cross-platform colored terminal text." 118 | category = "main" 119 | optional = false 120 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 121 | 122 | [[package]] 123 | name = "crashtest" 124 | version = "0.3.1" 125 | description = "Manage Python errors with ease" 126 | category = "main" 127 | optional = false 128 | python-versions = ">=3.6,<4.0" 129 | 130 | [[package]] 131 | name = "deprecated" 132 | version = "1.2.13" 133 | description = "Python @deprecated decorator to deprecate old python classes, functions or methods." 134 | category = "main" 135 | optional = false 136 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 137 | 138 | [package.dependencies] 139 | wrapt = ">=1.10,<2" 140 | 141 | [package.extras] 142 | dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] 143 | 144 | [[package]] 145 | name = "dnspython" 146 | version = "2.2.0" 147 | description = "DNS toolkit" 148 | category = "main" 149 | optional = false 150 | python-versions = ">=3.6,<4.0" 151 | 152 | [package.extras] 153 | dnssec = ["cryptography (>=2.6,<37.0)"] 154 | curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] 155 | doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] 156 | idna = ["idna (>=2.1,<4.0)"] 157 | trio = ["trio (>=0.14,<0.20)"] 158 | wmi = ["wmi (>=1.5.1,<2.0.0)"] 159 | 160 | [[package]] 161 | name = "email-validator" 162 | version = "1.1.3" 163 | description = "A robust email syntax and deliverability validation library for Python 2.x/3.x." 164 | category = "main" 165 | optional = false 166 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 167 | 168 | [package.dependencies] 169 | dnspython = ">=1.15.0" 170 | idna = ">=2.0.0" 171 | 172 | [[package]] 173 | name = "fastapi" 174 | version = "0.70.1" 175 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 176 | category = "main" 177 | optional = false 178 | python-versions = ">=3.6.1" 179 | 180 | [package.dependencies] 181 | pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" 182 | starlette = "0.16.0" 183 | 184 | [package.extras] 185 | all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "ujson (>=4.0.1,<5.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] 186 | dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] 187 | doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"] 188 | test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"] 189 | 190 | [[package]] 191 | name = "fastapi-cache2" 192 | version = "0.1.8" 193 | description = "Cache for FastAPI" 194 | category = "main" 195 | optional = false 196 | python-versions = ">=3.7,<4.0" 197 | 198 | [package.dependencies] 199 | aioredis = {version = ">=2.0,<3.0", optional = true, markers = "extra == \"redis\" or extra == \"all\""} 200 | fastapi = "*" 201 | pendulum = "*" 202 | uvicorn = "*" 203 | 204 | [package.extras] 205 | dynamodb = ["aiobotocore (>=1.4.1,<2.0.0)"] 206 | memcache = ["aiomcache"] 207 | all = ["aiomcache", "aioredis (>=2.0,<3.0)"] 208 | redis = ["aioredis (>=2.0,<3.0)"] 209 | 210 | [[package]] 211 | name = "h11" 212 | version = "0.13.0" 213 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 214 | category = "main" 215 | optional = false 216 | python-versions = ">=3.6" 217 | 218 | [package.dependencies] 219 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 220 | 221 | [[package]] 222 | name = "hiredis" 223 | version = "2.0.0" 224 | description = "Python wrapper for hiredis" 225 | category = "main" 226 | optional = false 227 | python-versions = ">=3.6" 228 | 229 | [[package]] 230 | name = "idna" 231 | version = "3.3" 232 | description = "Internationalized Domain Names in Applications (IDNA)" 233 | category = "main" 234 | optional = false 235 | python-versions = ">=3.5" 236 | 237 | [[package]] 238 | name = "importlib-metadata" 239 | version = "4.10.1" 240 | description = "Read metadata from Python packages" 241 | category = "main" 242 | optional = false 243 | python-versions = ">=3.7" 244 | 245 | [package.dependencies] 246 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 247 | zipp = ">=0.5" 248 | 249 | [package.extras] 250 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 251 | perf = ["ipython"] 252 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 253 | 254 | [[package]] 255 | name = "packaging" 256 | version = "21.3" 257 | description = "Core utilities for Python packages" 258 | category = "main" 259 | optional = false 260 | python-versions = ">=3.6" 261 | 262 | [package.dependencies] 263 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 264 | 265 | [[package]] 266 | name = "pendulum" 267 | version = "2.1.2" 268 | description = "Python datetimes made easy" 269 | category = "main" 270 | optional = false 271 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 272 | 273 | [package.dependencies] 274 | python-dateutil = ">=2.6,<3.0" 275 | pytzdata = ">=2020.1" 276 | 277 | [[package]] 278 | name = "pptree" 279 | version = "3.1" 280 | description = "Pretty print trees" 281 | category = "main" 282 | optional = false 283 | python-versions = "*" 284 | 285 | [[package]] 286 | name = "pycodestyle" 287 | version = "2.8.0" 288 | description = "Python style guide checker" 289 | category = "dev" 290 | optional = false 291 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 292 | 293 | [[package]] 294 | name = "pydantic" 295 | version = "1.9.0" 296 | description = "Data validation and settings management using python 3.6 type hinting" 297 | category = "main" 298 | optional = false 299 | python-versions = ">=3.6.1" 300 | 301 | [package.dependencies] 302 | email-validator = {version = ">=1.0.3", optional = true, markers = "extra == \"email\""} 303 | typing-extensions = ">=3.7.4.3" 304 | 305 | [package.extras] 306 | dotenv = ["python-dotenv (>=0.10.4)"] 307 | email = ["email-validator (>=1.0.3)"] 308 | 309 | [[package]] 310 | name = "pylev" 311 | version = "1.4.0" 312 | description = "A pure Python Levenshtein implementation that's not freaking GPL'd." 313 | category = "main" 314 | optional = false 315 | python-versions = "*" 316 | 317 | [[package]] 318 | name = "pyparsing" 319 | version = "3.0.7" 320 | description = "Python parsing module" 321 | category = "main" 322 | optional = false 323 | python-versions = ">=3.6" 324 | 325 | [package.extras] 326 | diagrams = ["jinja2", "railroad-diagrams"] 327 | 328 | [[package]] 329 | name = "python-dateutil" 330 | version = "2.8.2" 331 | description = "Extensions to the standard Python datetime module" 332 | category = "main" 333 | optional = false 334 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 335 | 336 | [package.dependencies] 337 | six = ">=1.5" 338 | 339 | [[package]] 340 | name = "python-dotenv" 341 | version = "0.19.2" 342 | description = "Read key-value pairs from a .env file and set them as environment variables" 343 | category = "main" 344 | optional = false 345 | python-versions = ">=3.5" 346 | 347 | [package.extras] 348 | cli = ["click (>=5.0)"] 349 | 350 | [[package]] 351 | name = "python-ulid" 352 | version = "1.0.3" 353 | description = "Universally Unique Lexicographically Sortable Identifier" 354 | category = "main" 355 | optional = false 356 | python-versions = "*" 357 | 358 | [[package]] 359 | name = "pytzdata" 360 | version = "2020.1" 361 | description = "The Olson timezone database for Python." 362 | category = "main" 363 | optional = false 364 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 365 | 366 | [[package]] 367 | name = "redis" 368 | version = "4.1.2" 369 | description = "Python client for Redis database and key-value store" 370 | category = "main" 371 | optional = false 372 | python-versions = ">=3.6" 373 | 374 | [package.dependencies] 375 | deprecated = ">=1.2.3" 376 | importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""} 377 | packaging = ">=20.4" 378 | 379 | [package.extras] 380 | hiredis = ["hiredis (>=1.0.0)"] 381 | ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] 382 | 383 | [[package]] 384 | name = "redis-om" 385 | version = "0.0.17" 386 | description = "Objecting mapping, and more, for Redis." 387 | category = "main" 388 | optional = false 389 | python-versions = ">=3.7,<4.0" 390 | 391 | [package.dependencies] 392 | aioredis = ">=2.0.0,<3.0.0" 393 | cleo = "1.0.0a4" 394 | click = ">=8.0.1,<9.0.0" 395 | hiredis = ">=2.0.0,<3.0.0" 396 | pptree = ">=3.1,<4.0" 397 | pydantic = ">=1.8.2,<2.0.0" 398 | python-dotenv = ">=0.19.1,<0.20.0" 399 | python-ulid = ">=1.0.3,<2.0.0" 400 | redis = ">=3.5.3,<5.0.0" 401 | six = ">=1.16.0,<2.0.0" 402 | types-redis = ">=3.5.9,<5.0.0" 403 | types-six = ">=1.16.1,<2.0.0" 404 | typing-extensions = ">=4.0.0,<5.0.0" 405 | 406 | [[package]] 407 | name = "requests" 408 | version = "2.27.1" 409 | description = "Python HTTP for Humans." 410 | category = "main" 411 | optional = false 412 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 413 | 414 | [package.dependencies] 415 | certifi = ">=2017.4.17" 416 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} 417 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} 418 | urllib3 = ">=1.21.1,<1.27" 419 | 420 | [package.extras] 421 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 422 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] 423 | 424 | [[package]] 425 | name = "six" 426 | version = "1.16.0" 427 | description = "Python 2 and 3 compatibility utilities" 428 | category = "main" 429 | optional = false 430 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 431 | 432 | [[package]] 433 | name = "sniffio" 434 | version = "1.2.0" 435 | description = "Sniff out which async library your code is running under" 436 | category = "main" 437 | optional = false 438 | python-versions = ">=3.5" 439 | 440 | [[package]] 441 | name = "starlette" 442 | version = "0.16.0" 443 | description = "The little ASGI library that shines." 444 | category = "main" 445 | optional = false 446 | python-versions = ">=3.6" 447 | 448 | [package.dependencies] 449 | anyio = ">=3.0.0,<4" 450 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 451 | 452 | [package.extras] 453 | full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "graphene"] 454 | 455 | [[package]] 456 | name = "toml" 457 | version = "0.10.2" 458 | description = "Python Library for Tom's Obvious, Minimal Language" 459 | category = "dev" 460 | optional = false 461 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 462 | 463 | [[package]] 464 | name = "types-redis" 465 | version = "4.1.15" 466 | description = "Typing stubs for redis" 467 | category = "main" 468 | optional = false 469 | python-versions = "*" 470 | 471 | [[package]] 472 | name = "types-six" 473 | version = "1.16.10" 474 | description = "Typing stubs for six" 475 | category = "main" 476 | optional = false 477 | python-versions = "*" 478 | 479 | [[package]] 480 | name = "typing-extensions" 481 | version = "4.0.1" 482 | description = "Backported and Experimental Type Hints for Python 3.6+" 483 | category = "main" 484 | optional = false 485 | python-versions = ">=3.6" 486 | 487 | [[package]] 488 | name = "urllib3" 489 | version = "1.26.8" 490 | description = "HTTP library with thread-safe connection pooling, file post, and more." 491 | category = "main" 492 | optional = false 493 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 494 | 495 | [package.extras] 496 | brotli = ["brotlipy (>=0.6.0)"] 497 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 498 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 499 | 500 | [[package]] 501 | name = "uvicorn" 502 | version = "0.15.0" 503 | description = "The lightning-fast ASGI server." 504 | category = "main" 505 | optional = false 506 | python-versions = "*" 507 | 508 | [package.dependencies] 509 | asgiref = ">=3.4.0" 510 | click = ">=7.0" 511 | h11 = ">=0.8" 512 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 513 | 514 | [package.extras] 515 | standard = ["websockets (>=9.1)", "httptools (>=0.2.0,<0.3.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] 516 | 517 | [[package]] 518 | name = "wrapt" 519 | version = "1.13.3" 520 | description = "Module for decorators, wrappers and monkey patching." 521 | category = "main" 522 | optional = false 523 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 524 | 525 | [[package]] 526 | name = "zipp" 527 | version = "3.7.0" 528 | description = "Backport of pathlib-compatible object wrapper for zip files" 529 | category = "main" 530 | optional = false 531 | python-versions = ">=3.7" 532 | 533 | [package.extras] 534 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 535 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 536 | 537 | [metadata] 538 | lock-version = "1.1" 539 | python-versions = "^3.7" 540 | content-hash = "083a358921d10fe83dbaf43c02934b61181cecc143519a571bd31cd8233dd8e1" 541 | 542 | [metadata.files] 543 | aioredis = [ 544 | {file = "aioredis-2.0.1-py3-none-any.whl", hash = "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6"}, 545 | {file = "aioredis-2.0.1.tar.gz", hash = "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e"}, 546 | ] 547 | anyio = [ 548 | {file = "anyio-3.5.0-py3-none-any.whl", hash = "sha256:b5fa16c5ff93fa1046f2eeb5bbff2dad4d3514d6cda61d02816dba34fa8c3c2e"}, 549 | {file = "anyio-3.5.0.tar.gz", hash = "sha256:a0aeffe2fb1fdf374a8e4b471444f0f3ac4fb9f5a5b542b48824475e0042a5a6"}, 550 | ] 551 | asgiref = [ 552 | {file = "asgiref-3.5.0-py3-none-any.whl", hash = "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"}, 553 | {file = "asgiref-3.5.0.tar.gz", hash = "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0"}, 554 | ] 555 | async-timeout = [ 556 | {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, 557 | {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, 558 | ] 559 | autopep8 = [ 560 | {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, 561 | {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, 562 | ] 563 | certifi = [ 564 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, 565 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, 566 | ] 567 | charset-normalizer = [ 568 | {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, 569 | {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, 570 | ] 571 | cleo = [ 572 | {file = "cleo-1.0.0a4-py3-none-any.whl", hash = "sha256:cdd0c3458c15ced3a9f0204b1e53a1b4bee3c56ebcb3ac54c872a56acc657a09"}, 573 | {file = "cleo-1.0.0a4.tar.gz", hash = "sha256:a103a065d031b7d936ee88a6b93086a69bd9c1b40fa2ebfe8c056285a66b481d"}, 574 | ] 575 | click = [ 576 | {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, 577 | {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, 578 | ] 579 | colorama = [ 580 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 581 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 582 | ] 583 | crashtest = [ 584 | {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"}, 585 | {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, 586 | ] 587 | deprecated = [ 588 | {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, 589 | {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, 590 | ] 591 | dnspython = [ 592 | {file = "dnspython-2.2.0-py3-none-any.whl", hash = "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44"}, 593 | {file = "dnspython-2.2.0.tar.gz", hash = "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6"}, 594 | ] 595 | email-validator = [ 596 | {file = "email_validator-1.1.3-py2.py3-none-any.whl", hash = "sha256:5675c8ceb7106a37e40e2698a57c056756bf3f272cfa8682a4f87ebd95d8440b"}, 597 | {file = "email_validator-1.1.3.tar.gz", hash = "sha256:aa237a65f6f4da067119b7df3f13e89c25c051327b2b5b66dc075f33d62480d7"}, 598 | ] 599 | fastapi = [ 600 | {file = "fastapi-0.70.1-py3-none-any.whl", hash = "sha256:5367226c7bcd7bfb2e17edaf225fd9a983095b1372281e9a3eb661336fb93748"}, 601 | {file = "fastapi-0.70.1.tar.gz", hash = "sha256:21d03979b5336375c66fa5d1f3126c6beca650d5d2166fbb78345a30d33c8d06"}, 602 | ] 603 | fastapi-cache2 = [ 604 | {file = "fastapi-cache2-0.1.8.tar.gz", hash = "sha256:e074a6dad600f9a19f0e7111f58350d6cc7c17d27a0bad1f67336d178b19387b"}, 605 | {file = "fastapi_cache2-0.1.8-py3-none-any.whl", hash = "sha256:7cdd5037cdc0b10a99c0effcef30c42e212f4fc20db49a472dbf0d42174e955a"}, 606 | ] 607 | h11 = [ 608 | {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"}, 609 | {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"}, 610 | ] 611 | hiredis = [ 612 | {file = "hiredis-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048"}, 613 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26"}, 614 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea"}, 615 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99"}, 616 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05"}, 617 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a"}, 618 | {file = "hiredis-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63"}, 619 | {file = "hiredis-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6"}, 620 | {file = "hiredis-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485"}, 621 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a"}, 622 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc"}, 623 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579"}, 624 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e"}, 625 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79"}, 626 | {file = "hiredis-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc"}, 627 | {file = "hiredis-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"}, 628 | {file = "hiredis-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb"}, 629 | {file = "hiredis-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5"}, 630 | {file = "hiredis-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298"}, 631 | {file = "hiredis-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d"}, 632 | {file = "hiredis-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db"}, 633 | {file = "hiredis-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048"}, 634 | {file = "hiredis-2.0.0-cp38-cp38-win32.whl", hash = "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426"}, 635 | {file = "hiredis-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581"}, 636 | {file = "hiredis-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5"}, 637 | {file = "hiredis-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e"}, 638 | {file = "hiredis-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce"}, 639 | {file = "hiredis-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443"}, 640 | {file = "hiredis-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0"}, 641 | {file = "hiredis-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e"}, 642 | {file = "hiredis-2.0.0-cp39-cp39-win32.whl", hash = "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d"}, 643 | {file = "hiredis-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9"}, 644 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54"}, 645 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27"}, 646 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d"}, 647 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163"}, 648 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a"}, 649 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87"}, 650 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41"}, 651 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0"}, 652 | {file = "hiredis-2.0.0.tar.gz", hash = "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a"}, 653 | ] 654 | idna = [ 655 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 656 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 657 | ] 658 | importlib-metadata = [ 659 | {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, 660 | {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, 661 | ] 662 | packaging = [ 663 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 664 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 665 | ] 666 | pendulum = [ 667 | {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"}, 668 | {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"}, 669 | {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"}, 670 | {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"}, 671 | {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"}, 672 | {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"}, 673 | {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"}, 674 | {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"}, 675 | {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"}, 676 | {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"}, 677 | {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"}, 678 | {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"}, 679 | {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"}, 680 | {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"}, 681 | {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"}, 682 | {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"}, 683 | {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"}, 684 | {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"}, 685 | {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"}, 686 | {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"}, 687 | {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"}, 688 | ] 689 | pptree = [ 690 | {file = "pptree-3.1.tar.gz", hash = "sha256:4dd0ba2f58000cbd29d68a5b64bac29bcb5a663642f79404877c0059668a69f6"}, 691 | ] 692 | pycodestyle = [ 693 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 694 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 695 | ] 696 | pydantic = [ 697 | {file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"}, 698 | {file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"}, 699 | {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"}, 700 | {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"}, 701 | {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"}, 702 | {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"}, 703 | {file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"}, 704 | {file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"}, 705 | {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"}, 706 | {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"}, 707 | {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"}, 708 | {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"}, 709 | {file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"}, 710 | {file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"}, 711 | {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"}, 712 | {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"}, 713 | {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"}, 714 | {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"}, 715 | {file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"}, 716 | {file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"}, 717 | {file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"}, 718 | {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"}, 719 | {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"}, 720 | {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"}, 721 | {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"}, 722 | {file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"}, 723 | {file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"}, 724 | {file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"}, 725 | {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"}, 726 | {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"}, 727 | {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"}, 728 | {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"}, 729 | {file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"}, 730 | {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"}, 731 | {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"}, 732 | ] 733 | pylev = [ 734 | {file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"}, 735 | {file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"}, 736 | ] 737 | pyparsing = [ 738 | {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, 739 | {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, 740 | ] 741 | python-dateutil = [ 742 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 743 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 744 | ] 745 | python-dotenv = [ 746 | {file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"}, 747 | {file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"}, 748 | ] 749 | python-ulid = [ 750 | {file = "python-ulid-1.0.3.tar.gz", hash = "sha256:5dd8b969312a40e2212cec9c1ad63f25d4b6eafd92ee3195883e0287b6e9d19e"}, 751 | {file = "python_ulid-1.0.3-py3-none-any.whl", hash = "sha256:8704dc20f547f531fe3a41d4369842d737a0f275403b909d0872e7ea0fe8d6f2"}, 752 | ] 753 | pytzdata = [ 754 | {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"}, 755 | {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"}, 756 | ] 757 | redis = [ 758 | {file = "redis-4.1.2-py3-none-any.whl", hash = "sha256:f13eea4254e302485add677cadedaf1305c1b3a4e07535e23b7b239798ce9301"}, 759 | {file = "redis-4.1.2.tar.gz", hash = "sha256:bf86397be532fc0a888d5976a5313a3a70d8f912d52bc0c09bffda4b8425a1d4"}, 760 | ] 761 | redis-om = [ 762 | {file = "redis-om-0.0.17.tar.gz", hash = "sha256:13a23bc4730bc39b0e6ab1d66069087963a4b07d8f79b008ae8864db31d51bc3"}, 763 | {file = "redis_om-0.0.17-py3-none-any.whl", hash = "sha256:2f49e441b7e3998cb04719008fb9ba29b2a1df2df68ace44842961dbca60ad21"}, 764 | ] 765 | requests = [ 766 | {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, 767 | {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, 768 | ] 769 | six = [ 770 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 771 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 772 | ] 773 | sniffio = [ 774 | {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, 775 | {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, 776 | ] 777 | starlette = [ 778 | {file = "starlette-0.16.0-py3-none-any.whl", hash = "sha256:38eb24bf705a2c317e15868e384c1b8a12ca396e5a3c3a003db7e667c43f939f"}, 779 | {file = "starlette-0.16.0.tar.gz", hash = "sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870"}, 780 | ] 781 | toml = [ 782 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 783 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 784 | ] 785 | types-redis = [ 786 | {file = "types-redis-4.1.15.tar.gz", hash = "sha256:651daaa4522f9067629cc278ccfc40c87bfba5c1f780721cd103b5150683157a"}, 787 | {file = "types_redis-4.1.15-py3-none-any.whl", hash = "sha256:c34927f7002b6d59744e52013a6e3bd2fc2197debbcace0cf6faf5eb71736b47"}, 788 | ] 789 | types-six = [ 790 | {file = "types-six-1.16.10.tar.gz", hash = "sha256:78e6c0fe40c2958e2a95ab1e1adfbf3dab56894dc8cf590dae18fd69f3179eff"}, 791 | {file = "types_six-1.16.10-py2.py3-none-any.whl", hash = "sha256:1038d7a9a7d70004d69c94df97aa375ec177c0ee604bccb91465b1506c3972aa"}, 792 | ] 793 | typing-extensions = [ 794 | {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, 795 | {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, 796 | ] 797 | urllib3 = [ 798 | {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, 799 | {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, 800 | ] 801 | uvicorn = [ 802 | {file = "uvicorn-0.15.0-py3-none-any.whl", hash = "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1"}, 803 | {file = "uvicorn-0.15.0.tar.gz", hash = "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"}, 804 | ] 805 | wrapt = [ 806 | {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, 807 | {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, 808 | {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, 809 | {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, 810 | {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, 811 | {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, 812 | {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, 813 | {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, 814 | {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, 815 | {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, 816 | {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, 817 | {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, 818 | {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, 819 | {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, 820 | {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, 821 | {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, 822 | {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, 823 | {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, 824 | {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, 825 | {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, 826 | {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, 827 | {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, 828 | {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, 829 | {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, 830 | {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, 831 | {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, 832 | {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, 833 | {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, 834 | {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, 835 | {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, 836 | {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, 837 | {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, 838 | {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, 839 | {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, 840 | {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, 841 | {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, 842 | {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, 843 | {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, 844 | {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, 845 | {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, 846 | {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, 847 | {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, 848 | {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, 849 | {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, 850 | {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, 851 | {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, 852 | {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, 853 | {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, 854 | {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, 855 | {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, 856 | {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, 857 | ] 858 | zipp = [ 859 | {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, 860 | {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, 861 | ] 862 | -------------------------------------------------------------------------------- /relationships/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "redis-om-python-retail" 3 | version = "0.0.1" 4 | description = "An example integration between Redis OM and FastAPI for a retail application" 5 | authors = ["Will Johnston "] 6 | license = "BSD-3-Clause" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | fastapi = "^0.70.0" 11 | uvicorn = "^0.15.0" 12 | pydantic = {extras = ["email"], version = "^1.8.2"} 13 | fastapi-cache2 = {extras = ["redis"], version = "^0.1.7"} 14 | redis-om = "^0.0.17" 15 | requests = "^2.27.1" 16 | 17 | [tool.poetry.dev-dependencies] 18 | autopep8 = "^1.6.0" 19 | 20 | [build-system] 21 | requires = ["poetry-core>=1.0.0"] 22 | build-backend = "poetry.core.masonry.api" 23 | -------------------------------------------------------------------------------- /revision-pattern/python/.env.example: -------------------------------------------------------------------------------- 1 | REDIS_OM_URL=redis://localhost:6379 -------------------------------------------------------------------------------- /revision-pattern/python/demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from datetime import datetime 3 | from typing import List, Optional 4 | from aredis_om import Field, connections, JsonModel, EmbeddedJsonModel 5 | from aredis_om.model import Migrator 6 | from utils import Base 7 | 8 | 9 | class Revision(EmbeddedJsonModel): 10 | title: str = Field(index=True) 11 | body: str = Field(index=True) 12 | author: str = Field(index=True) 13 | last_saved_by: str = Field(index=True) 14 | created_at: datetime.date = Field(index=True) 15 | updated_at: datetime.date = Field(index=True) 16 | 17 | 18 | class Post(JsonModel): 19 | title: str = Field(index=True) 20 | body: str = Field(index=True) 21 | author: str = Field(index=True) 22 | last_saved_by: Optional[str] = Field(index=True) 23 | created_at: Optional[datetime.date] = Field(index=True) 24 | updated_at: datetime.date = Field(index=True) 25 | revisions: Optional[List[Revision]] 26 | 27 | 28 | async def create_post(**args): 29 | dt = datetime.now().isoformat() 30 | post = Post( 31 | title=args["title"], 32 | body=args["body"], 33 | author=args["author"], 34 | last_saved_by=args["last_saved_by"], 35 | created_at=dt, 36 | updated_at=dt, 37 | revisions=[] 38 | ) 39 | 40 | return await post.save() 41 | 42 | 43 | async def update_post(id: str, **args): 44 | post = await Post.get(id) 45 | revision = Revision( 46 | title=post.title, 47 | body=post.body, 48 | author=post.author, 49 | last_saved_by=post.last_saved_by, 50 | created_at=post.created_at, 51 | updated_at=post.updated_at) 52 | 53 | post.revisions.insert(0, revision) 54 | post.title = args.get("title", post.title) 55 | post.body = args.get("body", post.body) 56 | post.author = args.get("author", post.author) 57 | post.last_saved_by = args.get("last_saved_by", post.last_saved_by) 58 | post.updated_at = datetime.now().isoformat() 59 | 60 | return await post.save() 61 | 62 | async def get_posts(): 63 | results = await connections \ 64 | .get_redis_connection() \ 65 | .execute_command( 66 | f'FT.SEARCH {Post.Meta.index_name} * LIMIT 0 10 RETURN 5 title body author created_at updated_at' 67 | ) 68 | 69 | return Post.from_redis(results) 70 | 71 | async def get_post(id: str): 72 | return await Post.get(id) 73 | -------------------------------------------------------------------------------- /revision-pattern/python/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "revision-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "cluster-key-slot": "^1.1.0", 8 | "dotenv": "^16.0.1", 9 | "redis": "^4.1.0", 10 | "generic-pool": "^3.8.2", 11 | "yallist": "^4.0.0", 12 | "redis-om": "^0.3.3", 13 | "tslib": "^2.4.0", 14 | "ulid": "^2.3.0" 15 | }, 16 | "devDependencies": {}, 17 | "scripts": { 18 | "start": "poetry run python demo.py" 19 | }, 20 | "author": "", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /revision-pattern/python/poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aioredis" 3 | version = "2.0.1" 4 | description = "asyncio (PEP 3156) Redis support" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.6" 8 | 9 | [package.dependencies] 10 | async-timeout = "*" 11 | typing-extensions = "*" 12 | 13 | [package.extras] 14 | hiredis = ["hiredis (>=1.0)"] 15 | 16 | [[package]] 17 | name = "async-timeout" 18 | version = "4.0.2" 19 | description = "Timeout context manager for asyncio programs" 20 | category = "main" 21 | optional = false 22 | python-versions = ">=3.6" 23 | 24 | [package.dependencies] 25 | typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} 26 | 27 | [[package]] 28 | name = "autopep8" 29 | version = "1.6.0" 30 | description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" 31 | category = "dev" 32 | optional = false 33 | python-versions = "*" 34 | 35 | [package.dependencies] 36 | pycodestyle = ">=2.8.0" 37 | toml = "*" 38 | 39 | [[package]] 40 | name = "cleo" 41 | version = "1.0.0a4" 42 | description = "Cleo allows you to create beautiful and testable command-line interfaces." 43 | category = "main" 44 | optional = false 45 | python-versions = ">=3.6,<4.0" 46 | 47 | [package.dependencies] 48 | crashtest = ">=0.3.1,<0.4.0" 49 | pylev = ">=1.3.0,<2.0.0" 50 | 51 | [[package]] 52 | name = "click" 53 | version = "8.1.3" 54 | description = "Composable command line interface toolkit" 55 | category = "main" 56 | optional = false 57 | python-versions = ">=3.7" 58 | 59 | [package.dependencies] 60 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 61 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 62 | 63 | [[package]] 64 | name = "colorama" 65 | version = "0.4.4" 66 | description = "Cross-platform colored terminal text." 67 | category = "main" 68 | optional = false 69 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 70 | 71 | [[package]] 72 | name = "crashtest" 73 | version = "0.3.1" 74 | description = "Manage Python errors with ease" 75 | category = "main" 76 | optional = false 77 | python-versions = ">=3.6,<4.0" 78 | 79 | [[package]] 80 | name = "deprecated" 81 | version = "1.2.13" 82 | description = "Python @deprecated decorator to deprecate old python classes, functions or methods." 83 | category = "main" 84 | optional = false 85 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 86 | 87 | [package.dependencies] 88 | wrapt = ">=1.10,<2" 89 | 90 | [package.extras] 91 | dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] 92 | 93 | [[package]] 94 | name = "dnspython" 95 | version = "2.2.1" 96 | description = "DNS toolkit" 97 | category = "main" 98 | optional = false 99 | python-versions = ">=3.6,<4.0" 100 | 101 | [package.extras] 102 | dnssec = ["cryptography (>=2.6,<37.0)"] 103 | curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] 104 | doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] 105 | idna = ["idna (>=2.1,<4.0)"] 106 | trio = ["trio (>=0.14,<0.20)"] 107 | wmi = ["wmi (>=1.5.1,<2.0.0)"] 108 | 109 | [[package]] 110 | name = "email-validator" 111 | version = "1.2.1" 112 | description = "A robust email syntax and deliverability validation library." 113 | category = "main" 114 | optional = false 115 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 116 | 117 | [package.dependencies] 118 | dnspython = ">=1.15.0" 119 | idna = ">=2.0.0" 120 | 121 | [[package]] 122 | name = "hiredis" 123 | version = "2.0.0" 124 | description = "Python wrapper for hiredis" 125 | category = "main" 126 | optional = false 127 | python-versions = ">=3.6" 128 | 129 | [[package]] 130 | name = "idna" 131 | version = "3.3" 132 | description = "Internationalized Domain Names in Applications (IDNA)" 133 | category = "main" 134 | optional = false 135 | python-versions = ">=3.5" 136 | 137 | [[package]] 138 | name = "importlib-metadata" 139 | version = "4.11.4" 140 | description = "Read metadata from Python packages" 141 | category = "main" 142 | optional = false 143 | python-versions = ">=3.7" 144 | 145 | [package.dependencies] 146 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 147 | zipp = ">=0.5" 148 | 149 | [package.extras] 150 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 151 | perf = ["ipython"] 152 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] 153 | 154 | [[package]] 155 | name = "packaging" 156 | version = "21.3" 157 | description = "Core utilities for Python packages" 158 | category = "main" 159 | optional = false 160 | python-versions = ">=3.6" 161 | 162 | [package.dependencies] 163 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 164 | 165 | [[package]] 166 | name = "pptree" 167 | version = "3.1" 168 | description = "Pretty print trees" 169 | category = "main" 170 | optional = false 171 | python-versions = "*" 172 | 173 | [[package]] 174 | name = "pycodestyle" 175 | version = "2.8.0" 176 | description = "Python style guide checker" 177 | category = "dev" 178 | optional = false 179 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 180 | 181 | [[package]] 182 | name = "pydantic" 183 | version = "1.9.1" 184 | description = "Data validation and settings management using python type hints" 185 | category = "main" 186 | optional = false 187 | python-versions = ">=3.6.1" 188 | 189 | [package.dependencies] 190 | email-validator = {version = ">=1.0.3", optional = true, markers = "extra == \"email\""} 191 | typing-extensions = ">=3.7.4.3" 192 | 193 | [package.extras] 194 | dotenv = ["python-dotenv (>=0.10.4)"] 195 | email = ["email-validator (>=1.0.3)"] 196 | 197 | [[package]] 198 | name = "pylev" 199 | version = "1.4.0" 200 | description = "A pure Python Levenshtein implementation that's not freaking GPL'd." 201 | category = "main" 202 | optional = false 203 | python-versions = "*" 204 | 205 | [[package]] 206 | name = "pyparsing" 207 | version = "3.0.9" 208 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 209 | category = "main" 210 | optional = false 211 | python-versions = ">=3.6.8" 212 | 213 | [package.extras] 214 | diagrams = ["railroad-diagrams", "jinja2"] 215 | 216 | [[package]] 217 | name = "python-ulid" 218 | version = "1.1.0" 219 | description = "Universally Unique Lexicographically Sortable Identifier" 220 | category = "main" 221 | optional = false 222 | python-versions = ">=3.7" 223 | 224 | [package.dependencies] 225 | importlib-metadata = {version = "*", markers = "python_version == \"3.7\""} 226 | 227 | [[package]] 228 | name = "redis" 229 | version = "4.3.3" 230 | description = "Python client for Redis database and key-value store" 231 | category = "main" 232 | optional = false 233 | python-versions = ">=3.6" 234 | 235 | [package.dependencies] 236 | async-timeout = ">=4.0.2" 237 | deprecated = ">=1.2.3" 238 | importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""} 239 | packaging = ">=20.4" 240 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 241 | 242 | [package.extras] 243 | hiredis = ["hiredis (>=1.0.0)"] 244 | ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] 245 | 246 | [[package]] 247 | name = "redis-om" 248 | version = "0.0.27" 249 | description = "Objecting mapping, and more, for Redis." 250 | category = "main" 251 | optional = false 252 | python-versions = ">=3.7,<4.0" 253 | 254 | [package.dependencies] 255 | aioredis = ">=2.0.0,<3.0.0" 256 | cleo = "1.0.0a4" 257 | click = ">=8.0.1,<9.0.0" 258 | hiredis = ">=2.0.0,<3.0.0" 259 | pptree = ">=3.1,<4.0" 260 | pydantic = ">=1.8.2,<2.0.0" 261 | python-ulid = ">=1.0.3,<2.0.0" 262 | redis = ">=3.5.3,<5.0.0" 263 | six = ">=1.16.0,<2.0.0" 264 | types-redis = ">=3.5.9,<5.0.0" 265 | types-six = ">=1.16.1,<2.0.0" 266 | typing-extensions = ">=4.0.0,<5.0.0" 267 | 268 | [[package]] 269 | name = "six" 270 | version = "1.16.0" 271 | description = "Python 2 and 3 compatibility utilities" 272 | category = "main" 273 | optional = false 274 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 275 | 276 | [[package]] 277 | name = "toml" 278 | version = "0.10.2" 279 | description = "Python Library for Tom's Obvious, Minimal Language" 280 | category = "dev" 281 | optional = false 282 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 283 | 284 | [[package]] 285 | name = "types-redis" 286 | version = "4.2.6" 287 | description = "Typing stubs for redis" 288 | category = "main" 289 | optional = false 290 | python-versions = "*" 291 | 292 | [[package]] 293 | name = "types-six" 294 | version = "1.16.15" 295 | description = "Typing stubs for six" 296 | category = "main" 297 | optional = false 298 | python-versions = "*" 299 | 300 | [[package]] 301 | name = "typing-extensions" 302 | version = "4.2.0" 303 | description = "Backported and Experimental Type Hints for Python 3.7+" 304 | category = "main" 305 | optional = false 306 | python-versions = ">=3.7" 307 | 308 | [[package]] 309 | name = "wrapt" 310 | version = "1.14.1" 311 | description = "Module for decorators, wrappers and monkey patching." 312 | category = "main" 313 | optional = false 314 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 315 | 316 | [[package]] 317 | name = "zipp" 318 | version = "3.8.0" 319 | description = "Backport of pathlib-compatible object wrapper for zip files" 320 | category = "main" 321 | optional = false 322 | python-versions = ">=3.7" 323 | 324 | [package.extras] 325 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 326 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] 327 | 328 | [metadata] 329 | lock-version = "1.1" 330 | python-versions = "^3.7" 331 | content-hash = "e9afcaa69afb7dde18941d019a00866ea9f7efc58a69294e5384d750e7051630" 332 | 333 | [metadata.files] 334 | aioredis = [ 335 | {file = "aioredis-2.0.1-py3-none-any.whl", hash = "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6"}, 336 | {file = "aioredis-2.0.1.tar.gz", hash = "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e"}, 337 | ] 338 | async-timeout = [ 339 | {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, 340 | {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, 341 | ] 342 | autopep8 = [ 343 | {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, 344 | {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, 345 | ] 346 | cleo = [ 347 | {file = "cleo-1.0.0a4-py3-none-any.whl", hash = "sha256:cdd0c3458c15ced3a9f0204b1e53a1b4bee3c56ebcb3ac54c872a56acc657a09"}, 348 | {file = "cleo-1.0.0a4.tar.gz", hash = "sha256:a103a065d031b7d936ee88a6b93086a69bd9c1b40fa2ebfe8c056285a66b481d"}, 349 | ] 350 | click = [ 351 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 352 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 353 | ] 354 | colorama = [ 355 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 356 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 357 | ] 358 | crashtest = [ 359 | {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"}, 360 | {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, 361 | ] 362 | deprecated = [ 363 | {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, 364 | {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, 365 | ] 366 | dnspython = [ 367 | {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, 368 | {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, 369 | ] 370 | email-validator = [ 371 | {file = "email_validator-1.2.1-py2.py3-none-any.whl", hash = "sha256:c8589e691cf73eb99eed8d10ce0e9cbb05a0886ba920c8bcb7c82873f4c5789c"}, 372 | {file = "email_validator-1.2.1.tar.gz", hash = "sha256:6757aea012d40516357c0ac2b1a4c31219ab2f899d26831334c5d069e8b6c3d8"}, 373 | ] 374 | hiredis = [ 375 | {file = "hiredis-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048"}, 376 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26"}, 377 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea"}, 378 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99"}, 379 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05"}, 380 | {file = "hiredis-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a"}, 381 | {file = "hiredis-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63"}, 382 | {file = "hiredis-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6"}, 383 | {file = "hiredis-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485"}, 384 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a"}, 385 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc"}, 386 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579"}, 387 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e"}, 388 | {file = "hiredis-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79"}, 389 | {file = "hiredis-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc"}, 390 | {file = "hiredis-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"}, 391 | {file = "hiredis-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb"}, 392 | {file = "hiredis-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5"}, 393 | {file = "hiredis-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298"}, 394 | {file = "hiredis-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d"}, 395 | {file = "hiredis-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db"}, 396 | {file = "hiredis-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048"}, 397 | {file = "hiredis-2.0.0-cp38-cp38-win32.whl", hash = "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426"}, 398 | {file = "hiredis-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581"}, 399 | {file = "hiredis-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5"}, 400 | {file = "hiredis-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e"}, 401 | {file = "hiredis-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce"}, 402 | {file = "hiredis-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443"}, 403 | {file = "hiredis-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0"}, 404 | {file = "hiredis-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e"}, 405 | {file = "hiredis-2.0.0-cp39-cp39-win32.whl", hash = "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d"}, 406 | {file = "hiredis-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9"}, 407 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54"}, 408 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27"}, 409 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d"}, 410 | {file = "hiredis-2.0.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163"}, 411 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a"}, 412 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87"}, 413 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41"}, 414 | {file = "hiredis-2.0.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0"}, 415 | {file = "hiredis-2.0.0.tar.gz", hash = "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a"}, 416 | ] 417 | idna = [ 418 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 419 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 420 | ] 421 | importlib-metadata = [ 422 | {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, 423 | {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, 424 | ] 425 | packaging = [ 426 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 427 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 428 | ] 429 | pptree = [ 430 | {file = "pptree-3.1.tar.gz", hash = "sha256:4dd0ba2f58000cbd29d68a5b64bac29bcb5a663642f79404877c0059668a69f6"}, 431 | ] 432 | pycodestyle = [ 433 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 434 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 435 | ] 436 | pydantic = [ 437 | {file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"}, 438 | {file = "pydantic-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320c64dd876e45254bdd350f0179da737463eea41c43bacbee9d8c9d1021f11"}, 439 | {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"}, 440 | {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11951b404e08b01b151222a1cb1a9f0a860a8153ce8334149ab9199cd198131"}, 441 | {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8bc541a405423ce0e51c19f637050acdbdf8feca34150e0d17f675e72d119580"}, 442 | {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e565a785233c2d03724c4dc55464559639b1ba9ecf091288dd47ad9c629433bd"}, 443 | {file = "pydantic-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4a88dcd6ff8fd47c18b3a3709a89adb39a6373f4482e04c1b765045c7e282fd"}, 444 | {file = "pydantic-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:447d5521575f18e18240906beadc58551e97ec98142266e521c34968c76c8761"}, 445 | {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985ceb5d0a86fcaa61e45781e567a59baa0da292d5ed2e490d612d0de5796918"}, 446 | {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059b6c1795170809103a1538255883e1983e5b831faea6558ef873d4955b4a74"}, 447 | {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d12f96b5b64bec3f43c8e82b4aab7599d0157f11c798c9f9c528a72b9e0b339a"}, 448 | {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ae72f8098acb368d877b210ebe02ba12585e77bd0db78ac04a1ee9b9f5dd2166"}, 449 | {file = "pydantic-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:79b485767c13788ee314669008d01f9ef3bc05db9ea3298f6a50d3ef596a154b"}, 450 | {file = "pydantic-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:494f7c8537f0c02b740c229af4cb47c0d39840b829ecdcfc93d91dcbb0779892"}, 451 | {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e"}, 452 | {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:969dd06110cb780da01336b281f53e2e7eb3a482831df441fb65dd30403f4608"}, 453 | {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:177071dfc0df6248fd22b43036f936cfe2508077a72af0933d0c1fa269b18537"}, 454 | {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9bcf8b6e011be08fb729d110f3e22e654a50f8a826b0575c7196616780683380"}, 455 | {file = "pydantic-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a955260d47f03df08acf45689bd163ed9df82c0e0124beb4251b1290fa7ae728"}, 456 | {file = "pydantic-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ce157d979f742a915b75f792dbd6aa63b8eccaf46a1005ba03aa8a986bde34a"}, 457 | {file = "pydantic-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0bf07cab5b279859c253d26a9194a8906e6f4a210063b84b433cf90a569de0c1"}, 458 | {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d93d4e95eacd313d2c765ebe40d49ca9dd2ed90e5b37d0d421c597af830c195"}, 459 | {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1542636a39c4892c4f4fa6270696902acb186a9aaeac6f6cf92ce6ae2e88564b"}, 460 | {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a9af62e9b5b9bc67b2a195ebc2c2662fdf498a822d62f902bf27cccb52dbbf49"}, 461 | {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"}, 462 | {file = "pydantic-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9f659a5ee95c8baa2436d392267988fd0f43eb774e5eb8739252e5a7e9cf07e0"}, 463 | {file = "pydantic-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b83ba3825bc91dfa989d4eed76865e71aea3a6ca1388b59fc801ee04c4d8d0d6"}, 464 | {file = "pydantic-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd8fecbad028cd89d04a46688d2fcc14423e8a196d5b0a5c65105664901f810"}, 465 | {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02eefd7087268b711a3ff4db528e9916ac9aa18616da7bca69c1871d0b7a091f"}, 466 | {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb57ba90929bac0b6cc2af2373893d80ac559adda6933e562dcfb375029acee"}, 467 | {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4ce9ae9e91f46c344bec3b03d6ee9612802682c1551aaf627ad24045ce090761"}, 468 | {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ccb318bf0c9ab97fc04c10c37683d9eea952ed526707fabf9ac5ae59b701fd"}, 469 | {file = "pydantic-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b6760b08b7c395975d893e0b814a11cf011ebb24f7d869e7118f5a339a82e1"}, 470 | {file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"}, 471 | {file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"}, 472 | ] 473 | pylev = [ 474 | {file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"}, 475 | {file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"}, 476 | ] 477 | pyparsing = [ 478 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 479 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 480 | ] 481 | python-ulid = [ 482 | {file = "python-ulid-1.1.0.tar.gz", hash = "sha256:5fb5e4a91db8ca93e8938a613360b3def299b60d41f847279a8c39c9b2e9c65e"}, 483 | {file = "python_ulid-1.1.0-py3-none-any.whl", hash = "sha256:88c952f6be133dbede19c907d72d26717d2691ec8421512b573144794d891e24"}, 484 | ] 485 | redis = [ 486 | {file = "redis-4.3.3-py3-none-any.whl", hash = "sha256:f57f8df5d238a8ecf92f499b6b21467bfee6c13d89953c27edf1e2bc673622e7"}, 487 | {file = "redis-4.3.3.tar.gz", hash = "sha256:2f7a57cf4af15cd543c4394bcbe2b9148db2606a37edba755368836e3a1d053e"}, 488 | ] 489 | redis-om = [ 490 | {file = "redis-om-0.0.27.tar.gz", hash = "sha256:03563be2570152436c833c6472b3a70f0baf53b46232c5811c5047e3a9f8c3be"}, 491 | {file = "redis_om-0.0.27-py3-none-any.whl", hash = "sha256:dbc59b39868acb5a311e384bfa52babf143f55cf6a27e6877447e433a7c9ab37"}, 492 | ] 493 | six = [ 494 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 495 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 496 | ] 497 | toml = [ 498 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 499 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 500 | ] 501 | types-redis = [ 502 | {file = "types-redis-4.2.6.tar.gz", hash = "sha256:d6adc77185cf40b300816767a64c0ee9ee0b21dc174e8e5c23b7e83d43189cb8"}, 503 | {file = "types_redis-4.2.6-py3-none-any.whl", hash = "sha256:1136af954ade0be33b487f440c8cbcbee29f089a83e685484ec91f363c6c69fe"}, 504 | ] 505 | types-six = [ 506 | {file = "types-six-1.16.15.tar.gz", hash = "sha256:d244f0537dab0d0570a5bc6f8a60c4da7f0546d960a8677520e6bff214a64fb8"}, 507 | {file = "types_six-1.16.15-py3-none-any.whl", hash = "sha256:18f6856a7df44fc7a292c2d73093908333e5f7cb858667b8cbefc8ed1e91942e"}, 508 | ] 509 | typing-extensions = [ 510 | {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, 511 | {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, 512 | ] 513 | wrapt = [ 514 | {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, 515 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, 516 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, 517 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, 518 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, 519 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, 520 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, 521 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, 522 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, 523 | {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, 524 | {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, 525 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, 526 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, 527 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, 528 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, 529 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, 530 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, 531 | {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, 532 | {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, 533 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, 534 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, 535 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, 536 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, 537 | {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, 538 | {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, 539 | {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, 540 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, 541 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, 542 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, 543 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, 544 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, 545 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, 546 | {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, 547 | {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, 548 | {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, 549 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, 550 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, 551 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, 552 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, 553 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, 554 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, 555 | {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, 556 | {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, 557 | {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, 558 | {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, 559 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, 560 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, 561 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, 562 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, 563 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, 564 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, 565 | {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, 566 | {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, 567 | {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, 568 | {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, 569 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, 570 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, 571 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, 572 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, 573 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, 574 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, 575 | {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, 576 | {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, 577 | {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, 578 | ] 579 | zipp = [ 580 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, 581 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, 582 | ] 583 | -------------------------------------------------------------------------------- /revision-pattern/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "revision-pattern" 3 | version = "0.0.1" 4 | description = "An example of how to use NoSQL with Redis Stack and RedisOM for document versioning" 5 | authors = ["Will Johnston "] 6 | license = "BSD-3-Clause" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | pydantic = {extras = ["email"], version = "^1.8.2"} 11 | redis-om = "^0.0.27" 12 | 13 | [tool.poetry.dev-dependencies] 14 | autopep8 = "^1.6.0" 15 | 16 | [build-system] 17 | requires = ["poetry-core>=1.0.0"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /revision-pattern/python/utils.py: -------------------------------------------------------------------------------- 1 | counts = {} 2 | 3 | 4 | def make_key(cls, part: str): 5 | model_prefix = getattr(cls._meta, "model_key_prefix", "").strip(":") 6 | return f"{model_prefix}:{part}" 7 | 8 | 9 | def get_id_creator(key: str): 10 | global counts 11 | 12 | if key not in counts: 13 | counts[key] = 0 14 | 15 | class PrimaryKeyCreator: 16 | def create_pk(self, *args, **kwargs) -> str: 17 | """Create a new primary key""" 18 | global counts 19 | counts[key] += 1 20 | return str(counts[key]) 21 | 22 | return PrimaryKeyCreator 23 | 24 | def get_meta(key: str): 25 | class Meta: 26 | model_key_prefix = key 27 | index_name = f"{key}:index" 28 | primary_key_creator_cls = get_id_creator(key) 29 | 30 | return Meta 31 | 32 | 33 | def Base(key: str): 34 | class Base: 35 | @classmethod 36 | def make_key(cls, part: str): 37 | return make_key(cls, part) 38 | 39 | Meta = get_meta(key) 40 | 41 | return Base 42 | -------------------------------------------------------------------------------- /schema-version-pattern/js/.env.example: -------------------------------------------------------------------------------- 1 | REDIS_URL=redis://localhost:6379 -------------------------------------------------------------------------------- /schema-version-pattern/js/client.js: -------------------------------------------------------------------------------- 1 | import { Client } from 'redis-om'; 2 | import { config } from 'dotenv'; 3 | 4 | config(); 5 | 6 | const clientPromise = new Promise(async (resolve) => { 7 | const client = new Client(); 8 | 9 | await client.open(process.env.REDIS_URL); 10 | 11 | resolve(client); 12 | }); 13 | 14 | /** 15 | * 16 | * @returns {Promise} 17 | */ 18 | export async function getClient() { 19 | return clientPromise; 20 | } 21 | -------------------------------------------------------------------------------- /schema-version-pattern/js/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schema-version-pattern", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "schema-version-pattern", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dotenv": "^16.0.1", 13 | "redis": "^4.0.4", 14 | "redis-om": "^0.3.3", 15 | "tslib": "^2.4.0" 16 | }, 17 | "engines": { 18 | "node": ">=17" 19 | } 20 | }, 21 | "node_modules/@redis/bloom": { 22 | "version": "1.0.2", 23 | "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", 24 | "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", 25 | "peerDependencies": { 26 | "@redis/client": "^1.0.0" 27 | } 28 | }, 29 | "node_modules/@redis/client": { 30 | "version": "1.1.0", 31 | "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.1.0.tgz", 32 | "integrity": "sha512-xO9JDIgzsZYDl3EvFhl6LC52DP3q3GCMUer8zHgKV6qSYsq1zB+pZs9+T80VgcRogrlRYhi4ZlfX6A+bHiBAgA==", 33 | "dependencies": { 34 | "cluster-key-slot": "1.1.0", 35 | "generic-pool": "3.8.2", 36 | "yallist": "4.0.0" 37 | }, 38 | "engines": { 39 | "node": ">=14" 40 | } 41 | }, 42 | "node_modules/@redis/graph": { 43 | "version": "1.0.1", 44 | "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", 45 | "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", 46 | "peerDependencies": { 47 | "@redis/client": "^1.0.0" 48 | } 49 | }, 50 | "node_modules/@redis/json": { 51 | "version": "1.0.3", 52 | "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", 53 | "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", 54 | "peerDependencies": { 55 | "@redis/client": "^1.0.0" 56 | } 57 | }, 58 | "node_modules/@redis/search": { 59 | "version": "1.0.6", 60 | "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", 61 | "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", 62 | "peerDependencies": { 63 | "@redis/client": "^1.0.0" 64 | } 65 | }, 66 | "node_modules/@redis/time-series": { 67 | "version": "1.0.3", 68 | "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", 69 | "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", 70 | "peerDependencies": { 71 | "@redis/client": "^1.0.0" 72 | } 73 | }, 74 | "node_modules/cluster-key-slot": { 75 | "version": "1.1.0", 76 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 77 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", 78 | "engines": { 79 | "node": ">=0.10.0" 80 | } 81 | }, 82 | "node_modules/dotenv": { 83 | "version": "16.0.1", 84 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", 85 | "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", 86 | "engines": { 87 | "node": ">=12" 88 | } 89 | }, 90 | "node_modules/generic-pool": { 91 | "version": "3.8.2", 92 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", 93 | "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==", 94 | "engines": { 95 | "node": ">= 4" 96 | } 97 | }, 98 | "node_modules/redis": { 99 | "version": "4.1.0", 100 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.1.0.tgz", 101 | "integrity": "sha512-5hvJ8wbzpCCiuN1ges6tx2SAh2XXCY0ayresBmu40/SGusWHFW86TAlIPpbimMX2DFHOX7RN34G2XlPA1Z43zg==", 102 | "dependencies": { 103 | "@redis/bloom": "1.0.2", 104 | "@redis/client": "1.1.0", 105 | "@redis/graph": "1.0.1", 106 | "@redis/json": "1.0.3", 107 | "@redis/search": "1.0.6", 108 | "@redis/time-series": "1.0.3" 109 | } 110 | }, 111 | "node_modules/redis-om": { 112 | "version": "0.3.3", 113 | "resolved": "https://registry.npmjs.org/redis-om/-/redis-om-0.3.3.tgz", 114 | "integrity": "sha512-mEgKZLnzK9aY+ElETJ+GaDEAdu8IiBM+9sHocfNXvidb8lo8V+6djm5uG5KBdcmConyntbpTGL4QlXlVbp9Auw==", 115 | "dependencies": { 116 | "redis": "^4.0.4", 117 | "ulid": "^2.3.0" 118 | }, 119 | "engines": { 120 | "node": ">= 14" 121 | } 122 | }, 123 | "node_modules/tslib": { 124 | "version": "2.4.0", 125 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 126 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" 127 | }, 128 | "node_modules/ulid": { 129 | "version": "2.3.0", 130 | "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", 131 | "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", 132 | "bin": { 133 | "ulid": "bin/cli.js" 134 | } 135 | }, 136 | "node_modules/yallist": { 137 | "version": "4.0.0", 138 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 139 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 140 | } 141 | }, 142 | "dependencies": { 143 | "@redis/bloom": { 144 | "version": "1.0.2", 145 | "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", 146 | "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", 147 | "requires": {} 148 | }, 149 | "@redis/client": { 150 | "version": "1.1.0", 151 | "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.1.0.tgz", 152 | "integrity": "sha512-xO9JDIgzsZYDl3EvFhl6LC52DP3q3GCMUer8zHgKV6qSYsq1zB+pZs9+T80VgcRogrlRYhi4ZlfX6A+bHiBAgA==", 153 | "requires": { 154 | "cluster-key-slot": "1.1.0", 155 | "generic-pool": "3.8.2", 156 | "yallist": "4.0.0" 157 | } 158 | }, 159 | "@redis/graph": { 160 | "version": "1.0.1", 161 | "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", 162 | "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", 163 | "requires": {} 164 | }, 165 | "@redis/json": { 166 | "version": "1.0.3", 167 | "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", 168 | "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", 169 | "requires": {} 170 | }, 171 | "@redis/search": { 172 | "version": "1.0.6", 173 | "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", 174 | "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", 175 | "requires": {} 176 | }, 177 | "@redis/time-series": { 178 | "version": "1.0.3", 179 | "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", 180 | "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", 181 | "requires": {} 182 | }, 183 | "cluster-key-slot": { 184 | "version": "1.1.0", 185 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 186 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" 187 | }, 188 | "dotenv": { 189 | "version": "16.0.1", 190 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", 191 | "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" 192 | }, 193 | "generic-pool": { 194 | "version": "3.8.2", 195 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", 196 | "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==" 197 | }, 198 | "redis": { 199 | "version": "4.1.0", 200 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.1.0.tgz", 201 | "integrity": "sha512-5hvJ8wbzpCCiuN1ges6tx2SAh2XXCY0ayresBmu40/SGusWHFW86TAlIPpbimMX2DFHOX7RN34G2XlPA1Z43zg==", 202 | "requires": { 203 | "@redis/bloom": "1.0.2", 204 | "@redis/client": "1.1.0", 205 | "@redis/graph": "1.0.1", 206 | "@redis/json": "1.0.3", 207 | "@redis/search": "1.0.6", 208 | "@redis/time-series": "1.0.3" 209 | } 210 | }, 211 | "redis-om": { 212 | "version": "0.3.3", 213 | "resolved": "https://registry.npmjs.org/redis-om/-/redis-om-0.3.3.tgz", 214 | "integrity": "sha512-mEgKZLnzK9aY+ElETJ+GaDEAdu8IiBM+9sHocfNXvidb8lo8V+6djm5uG5KBdcmConyntbpTGL4QlXlVbp9Auw==", 215 | "requires": { 216 | "redis": "^4.0.4", 217 | "ulid": "^2.3.0" 218 | } 219 | }, 220 | "tslib": { 221 | "version": "2.4.0", 222 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 223 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" 224 | }, 225 | "ulid": { 226 | "version": "2.3.0", 227 | "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", 228 | "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==" 229 | }, 230 | "yallist": { 231 | "version": "4.0.0", 232 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 233 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /schema-version-pattern/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schema-version-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "engines": { 8 | "node": ">=17" 9 | }, 10 | "scripts": { 11 | "test": "echo %npm_package_name% && exit 1", 12 | "compose": "docker-compose -p %npm_package_name% -f docker-compose.yml up -d", 13 | "part1": "node part1.js", 14 | "part2": "node part2.js", 15 | "generate": "node generate.js" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "dependencies": { 20 | "dotenv": "^16.0.1", 21 | "redis": "^4.0.4", 22 | "redis-om": "^0.3.3", 23 | "tslib": "^2.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /schema-version-pattern/js/part1.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Schema, Entity } from 'redis-om'; 3 | import { getClient } from './client.js'; 4 | 5 | class User extends Entity {} 6 | 7 | const userSchema = new Schema(User, { 8 | name: { type: 'string' }, 9 | email: { type: 'string' }, 10 | }); 11 | 12 | export async function create(data) { 13 | const client = await getClient(); 14 | const repo = client.fetchRepository(userSchema); 15 | const user = repo.createEntity(data); 16 | 17 | await repo.save(user); 18 | 19 | return user.toJSON(); 20 | } 21 | 22 | export async function read(id) { 23 | const client = await getClient(); 24 | const repo = client.fetchRepository(userSchema); 25 | const user = await repo.fetch(id); 26 | 27 | return user.toJSON(); 28 | } 29 | 30 | export async function update(id, data) { 31 | const client = await getClient(); 32 | const repo = client.fetchRepository(userSchema); 33 | const user = await repo.fetch(id); 34 | 35 | user.name = data.name; 36 | user.email = data.email; 37 | 38 | await repo.save(user); 39 | 40 | return user.toJSON(); 41 | } 42 | 43 | try { 44 | const user = await create({ 45 | name: 'Jane Doe', 46 | email: 'janedoe@gmail.com' 47 | }); 48 | 49 | console.log(user); 50 | process.exit(); 51 | } catch (e) { 52 | console.log(e); 53 | process.exit(1); 54 | } 55 | -------------------------------------------------------------------------------- /schema-version-pattern/js/part2.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Schema, Entity } from 'redis-om'; 3 | import { getClient } from './client.js'; 4 | 5 | class User extends Entity {} 6 | 7 | const userSchema = new Schema(User, { 8 | schema: { type: 'string' }, 9 | name: { type: 'string' }, 10 | email: { type: 'string' }, 11 | contact: { type: 'string' }, 12 | emails: { type: 'string[]' }, 13 | }); 14 | 15 | function translate(data, schema = '1') { 16 | // Ignore data if using the old schema 17 | if (schema === '1') { 18 | return data; 19 | } 20 | 21 | // Ignore data if already using schema '2' 22 | if (schema === '2' && data.schema === '2') { 23 | return data; 24 | } 25 | 26 | // Migrate old data to new schema 27 | data.schema = schema; 28 | data.emails = [data.email]; 29 | data.contact = data.email; 30 | data.email = null; 31 | 32 | return data; 33 | } 34 | 35 | export async function create(data, schema = '1') { 36 | const client = await getClient(); 37 | const repo = client.fetchRepository(userSchema); 38 | const user = repo.createEntity(translate(data, schema)); 39 | 40 | await repo.save(user); 41 | 42 | return user.toJSON(); 43 | } 44 | 45 | export async function update(id, data, schema = '1') { 46 | const client = await getClient(); 47 | const repo = client.fetchRepository(userSchema); 48 | const user = await repo.fetch(id); 49 | 50 | data = translate(data, schema); 51 | 52 | if (schema === '1') { 53 | user.schema = schema; 54 | user.name = data.name; 55 | user.email = data.email; 56 | } else { 57 | user.schema = data.schema; 58 | user.name = data.name; 59 | user.email = data.email; 60 | user.contact = data.contact; 61 | user.emails = data.emails; 62 | } 63 | 64 | await repo.save(user); 65 | 66 | return user.toJSON(); 67 | } 68 | -------------------------------------------------------------------------------- /tree-and-graph-pattern/README.md: -------------------------------------------------------------------------------- 1 | Use the [RedisGraph bulk loader](https://github.com/redisgraph/redisgraph-bulk-loader) to perform the following command in the `data` directory. 2 | 3 | ``` 4 | redisgraph-bulk-insert Org -n Location.csv -n Employee.csv -r REPORTS_TO.csv -r WORKS_AT.csv 5 | ``` 6 | 7 | Then look at `queries.redis` for some example queries to run in RedisInsight. 8 | -------------------------------------------------------------------------------- /tree-and-graph-pattern/data/Employee.csv: -------------------------------------------------------------------------------- 1 | name,title 2 | Doug,CEO 3 | Otto,CTO 4 | Cody,VP Engineering 5 | Carson,Developer 6 | Ima,Developer 7 | Cam,VP Developer Relations 8 | Robin,Developer Advocate 9 | Wanda,Developer Advocate 10 | Val,CMO 11 | Rich,VP Brand Marketing 12 | Luke,Brand Strategist 13 | Juana,Brand Strategist 14 | Roman,VP Digital Marketing 15 | Sally,Digital Strategist 16 | Count,Digital Strategist 17 | Phil,COO 18 | Buddy,Account Manager 19 | Owen,Account Manager 20 | Lynn,Account Manager 21 | Bob,Account Manager -------------------------------------------------------------------------------- /tree-and-graph-pattern/data/Location.csv: -------------------------------------------------------------------------------- 1 | name 2 | San Francisco 3 | Seattle 4 | DC -------------------------------------------------------------------------------- /tree-and-graph-pattern/data/REPORTS_TO.csv: -------------------------------------------------------------------------------- 1 | src,dest,relationship 2 | Otto,Doug,Otto->Doug 3 | Val,Doug,Val->Doug 4 | Phil,Doug,Phil->Doug 5 | Cody,Otto,Cody->Otto 6 | Cam,Otto,Cam->Otto 7 | Carson,Cody,Carson->Cody 8 | Ima,Cody,Ima->Cody 9 | Robin,Cam,Robin->Cam 10 | Wanda,Cam,Wanda->Cam 11 | Rich,Val,Rich->Val 12 | Roman,Val,Roman->Val 13 | Luke,Rich,Luke->Rich 14 | Juana,Rich,Juana->Rich 15 | Sally,Roman,Sally->Roman 16 | Count,Roman,Count->Roman 17 | Buddy,Phil,Buddy->Phil 18 | Owen,Phil,Owen->Phil 19 | Lynn,Phil,Lynn->Phil 20 | Bob,Phil,Bob->Phil -------------------------------------------------------------------------------- /tree-and-graph-pattern/data/WORKS_AT.csv: -------------------------------------------------------------------------------- 1 | src_employee,dest_location,full_time 2 | Doug,San Francisco,Full Time 3 | Otto,San Francisco,Full Time 4 | Carson,San Francisco,Full Time 5 | Ima,San Francisco,Full Time 6 | Robin,San Francisco,Full Time 7 | Wanda,San Francisco,Full Time 8 | Luke,San Francisco,Full Time 9 | Roman,San Francisco,Full Time 10 | Count,San Francisco,Part Time 11 | Lynn,San Francisco,Full Time 12 | Bob,San Francisco,Full Time 13 | Cody,Seattle,Full Time 14 | Cam,Seattle,Full Time 15 | Val,Seattle,Full Time 16 | Rich,Seattle,Part Time 17 | Juana,Seattle,Full Time 18 | Sally,DC,Full Time 19 | Phil,DC,Full Time 20 | Buddy,DC,Part Time 21 | Owen,DC,Part Time -------------------------------------------------------------------------------- /tree-and-graph-pattern/queries.redis: -------------------------------------------------------------------------------- 1 | GRAPH.QUERY Org "MATCH (e:Employee)-[:REPORTS_TO]->(m:Employee { name: 'Doug' }) RETURN e,m" 2 | GRAPH.QUERY Org "MATCH (e:Employee)-[:WORKS_AT]->(l:Location { name: 'Seattle' }) RETURN e,l" --------------------------------------------------------------------------------