forked from sopherapps/pydantic-redis
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: json_object_hook and serializer example (#294)
This adds a new feature, the ability to customize the json deserialization into objects using a json.loads object hook. it also updates documentation and includes an example of customizing serialization and deserialization of python objects #289
- Loading branch information
1 parent
5a94170
commit 80c725e
Showing
5 changed files
with
203 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
start-redis: ## Runs a copy of redis in docker | ||
docker run -it -d --rm --name pydantic-aioredis-example -p 6379:6379 -e REDIS_PASSWORD=password bitnami/redis || echo "$(REDIS_CONTAINER_NAME) is either running or failed" | ||
|
||
stop-redis: ## Stops the redis in docker | ||
docker stop pydantic-aioredis-example |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# asyncio_example | ||
|
||
This is a working example using python-aioredis with asyncio and a custom serializer for a python object BookCover. | ||
|
||
Book.json_default is used to serialize the BookCover object to a dictionary that json.dumps can dump to a string and store in redis. | ||
Book.json_object_hook can convert a dict from redis back to a BookCover object. | ||
|
||
# Requirements | ||
|
||
This example requires a running redis server. You can change the RedisConfig on line 28 in the example to match connecting to your running redis. | ||
|
||
For your ease of use, we've provided a Makefile in this directory that can start and stop a redis using docker. | ||
|
||
`make start-redis` | ||
|
||
`make stop-redis` | ||
|
||
The example is configured to connect to this dockerized redis automatically | ||
|
||
# Expected Output | ||
|
||
This is a working example. If you try to run it and find it broken, first check your local env. If you are unable to get the | ||
example running, please raise an Issue | ||
|
||
```bash | ||
python custom_serializer.py | ||
[Book(title='Great Expectations', author='Charles Dickens', published_on=datetime.date(1220, 4, 4), cover=<__main__.BookCover object at 0x10410c4c0>), Book(title='Jane Eyre', author='Charlotte Bronte', published_on=datetime.date(1225, 6, 4), cover=<__main__.BookCover object at 0x10410d4e0>), Book(title='Moby Dick', author='Herman Melville', published_on=datetime.date(1851, 10, 18), cover=<__main__.BookCover object at 0x10410d060>), Book(title='Oliver Twist', author='Charles Dickens', published_on=datetime.date(1215, 4, 4), cover=<__main__.BookCover object at 0x10410c760>), Book(title='Wuthering Heights', author='Emily Bronte', published_on=datetime.date(1600, 4, 4), cover=<__main__.BookCover object at 0x10410d690>)] | ||
[Book(title='Jane Eyre', author='Charlotte Bronte', published_on=datetime.date(1225, 6, 4), cover=<__main__.BookCover object at 0x10410cdc0>), Book(title='Oliver Twist', author='Charles Dickens', published_on=datetime.date(1215, 4, 4), cover=<__main__.BookCover object at 0x10410d7e0>)] | ||
[{'author': 'Charles Dickens', 'cover': <__main__.BookCover object at 0x10410d7b0>}, {'author': 'Charlotte Bronte', 'cover': <__main__.BookCover object at 0x10410d8d0>}, {'author': 'Herman Melville', 'cover': <__main__.BookCover object at 0x10410d840>}, {'author': 'Charles Dickens', 'cover': <__main__.BookCover object at 0x10410d960>}, {'author': 'Emily Bronte', 'cover': <__main__.BookCover object at 0x10410d900>}] | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import asyncio | ||
import json | ||
from datetime import date | ||
from datetime import datetime | ||
from typing import Any | ||
from typing import Dict | ||
from typing import List | ||
from typing import Optional | ||
|
||
from pydantic_aioredis import Model | ||
from pydantic_aioredis import RedisConfig | ||
from pydantic_aioredis import Store | ||
from pydantic_aioredis.abstract import STR_DUMP_SHAPES | ||
|
||
|
||
class BookCover: | ||
def __init__(self, cover_url: int, cover_size_x: int, cover_size_y: int): | ||
self.cover_url = cover_url | ||
self.cover_size_x = cover_size_x | ||
self.cover_size_y = cover_size_y | ||
|
||
@property | ||
def area(self): | ||
return self.cover_size_x * self.cover_size_y | ||
|
||
|
||
# Create models as you would create pydantic models i.e. using typings | ||
class Book(Model): | ||
_primary_key_field: str = "title" | ||
title: str | ||
author: str | ||
published_on: date | ||
cover: BookCover | ||
|
||
@classmethod | ||
def json_default(cls, obj: Any) -> str: | ||
"""Since BookCover can't be directly json serialized, we have to write our own json_default to serialize it methods to handle it.""" | ||
if isinstance(obj, BookCover): | ||
return { | ||
"__BookCover__": True, | ||
"cover_url": obj.cover_url, | ||
"cover_size_x": obj.cover_size_x, | ||
"cover_size_y": obj.cover_size_y, | ||
} | ||
|
||
return super().json_default(obj) | ||
|
||
@classmethod | ||
def json_object_hook(cls, obj: dict): | ||
"""Since we're serializing BookCovers above, we need to write an object hook to turn them back into an Object""" | ||
if obj.get("__BookCover__", False): | ||
return BookCover( | ||
cover_url=obj["cover_url"], | ||
cover_size_x=obj["cover_size_x"], | ||
cover_size_y=obj["cover_size_y"], | ||
) | ||
super().json_object_hook(obj) | ||
|
||
|
||
# Redisconfig. Change this configuration to match your redis server | ||
redis_config = RedisConfig( | ||
db=5, host="localhost", password="password", ssl=False, port=6379 | ||
) | ||
|
||
|
||
# Create the store and register your models | ||
store = Store(name="some_name", redis_config=redis_config, life_span_in_seconds=3600) | ||
store.register_model(Book) | ||
|
||
|
||
# Sample books. You can create as many as you wish anywhere in the code | ||
books = [ | ||
Book( | ||
title="Oliver Twist", | ||
author="Charles Dickens", | ||
published_on=date(year=1215, month=4, day=4), | ||
cover=BookCover( | ||
"https://images-na.ssl-images-amazon.com/images/I/51SmEM7LUGL._SX342_SY445_QL70_FMwebp_.jpg", | ||
333, | ||
499, | ||
), | ||
), | ||
Book( | ||
title="Great Expectations", | ||
author="Charles Dickens", | ||
published_on=date(year=1220, month=4, day=4), | ||
cover=BookCover( | ||
"https://images-na.ssl-images-amazon.com/images/I/51i715XqsYL._SX311_BO1,204,203,200_.jpg", | ||
333, | ||
499, | ||
), | ||
), | ||
Book( | ||
title="Jane Eyre", | ||
author="Charlotte Bronte", | ||
published_on=date(year=1225, month=6, day=4), | ||
cover=BookCover( | ||
"https://images-na.ssl-images-amazon.com/images/I/41saarVx+GL._SX324_BO1,204,203,200_.jpg", | ||
333, | ||
499, | ||
), | ||
), | ||
Book( | ||
title="Wuthering Heights", | ||
author="Emily Bronte", | ||
published_on=date(year=1600, month=4, day=4), | ||
cover=BookCover( | ||
"https://images-na.ssl-images-amazon.com/images/I/51ZKox7zBKL._SX338_BO1,204,203,200_.jpg", | ||
333, | ||
499, | ||
), | ||
), | ||
] | ||
|
||
|
||
async def work_with_orm(): | ||
# Insert them into redis | ||
await Book.insert(books) | ||
|
||
# Select all books to view them. A list of Model instances will be returned | ||
all_books = await Book.select() | ||
print(all_books) # Will print [Book(title="Oliver Twist", author="Charles Dickens", | ||
# published_on=date(year=1215, month=4, day=4), in_stock=False), Book(...] | ||
|
||
# Or select some of the books | ||
some_books = await Book.select(ids=["Oliver Twist", "Jane Eyre"]) | ||
print(some_books) # Will return only those two books | ||
|
||
# Or select some of the columns. THIS RETURNS DICTIONARIES not MODEL Instances | ||
# The Dictionaries have values in string form so you might need to do some extra work | ||
books_with_few_fields = await Book.select(columns=["author", "cover"]) | ||
print( | ||
books_with_few_fields | ||
) # Will print [{"author": "'Charles Dickens", "covker": Cover},...] | ||
|
||
# When _auto_sync = True (default), updating any attribute will update that field in Redis too | ||
this_book = Book( | ||
title="Moby Dick", | ||
author="Herman Melvill", | ||
published_on=date(year=1851, month=10, day=18), | ||
cover=BookCover( | ||
"https://m.media-amazon.com/images/I/411a8Moy1mL._SY346_.jpg", 333, 499 | ||
), | ||
) | ||
await Book.insert(this_book) | ||
# oops, there was a typo. Fix it | ||
this_book.author = "Herman Melville" | ||
this_book_from_redis = await Book.select(ids=["Moby Dick"]) | ||
assert this_book_from_redis[0].author == "Herman Melville" | ||
|
||
# If you have _auto_save set to false on a model, you have to await .save() to update a model in tedis | ||
await this_book.save() | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(work_with_orm()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters