Package: ddb-single
DynamoDB SingleTablehttps://pypi.org/project/ddb-single/Python DynamoDB interface, specialized in single-table design.
DynamoDB is high-performance serverless NoSQL, but difficult to disign tables.Single-table design needs only single table, and few GSIs (Global Secondary Indexes).
It makes effective and easy to manage your whole data models for single service.Getting StartedInstallpip install ddb-singleInit Tablefromddb_singleimportTabletable=Table(table_name="sample",endpoint_url="http://localhost:8000",)table.init()Data ModelsEach model has al least 3 keysprimary_key ... Hash key for single item. default:pk: {__model_name__}_{uuid}seconday_key ... Range key for item. default:sk: {__model_name__}_itemunique_key ... key to identify the item is the same. Mainly used to update item.And you can setserch_keyto enable search via GSIfromddb_singleimportBaseModel,DBField,FieldTypeclassUser(BaseModel):__table__=table__model_name__="user"name=DBField(unique_key=True)email=DBField(search_key=True)age=DBField(type=FieldType.NUMBER,search_key=True)description=DBField()Usageneed "Qurey" object for CRUDquery.model(foo).createquery.model(foo).getquery.model(foo).searchquery.model(foo).updatequery.model(foo).deletefromddb_singleimportQueryquery=Query(table)Create ItemIf the item with same value ofunique_keyalready exist, exist item is updated.user=User(name="John",email="john@example.com",description="test")query.model(user).create()Then, multible items added.pkskdatanameemaildescriptionuser_xxxxuser_itemJohnjohn@example.comtestuser_xxxxsearch_user_nameJohnuser_xxxxsearch_user_emailnew-john@example.comIn addition to main item (sk=user_item), multiple item (sk=search_{__model_name__}_{field_name}) added to table.
Those "search items" are used to searchThe GSIDataSearchIndexis used to get "search items" to extract target's pk.
Then,batch_getitems by pk.sk = hashdata = rangepksearch_user_nameJohnuser_xxxxsearch_user_emailnew-john@example.comuser_xxxxSearch Itemsuser=query.model(User).search(User.name.eq("John"))print(user)# -> [{"pk":"user_xxxx", "sk":"user_item", "name":"John", "email":"john@example.com"}]pk_only=Trueto extract pk withoutbatch_getuser_pks=query.model(User).search(User.name.eq("John"),pk_only=True)print(user_pks)# -> ["user_xxxx"]Get single itemget(pk)to get single item.user = query.model(User).get("user_xxxx")
print(user)
# -> {"pk":"user_xxxx", "sk":"user_item", "name":"John", "email":"john@example.com"}get_by_uniqueto get item byunique_keyuser=query.model(User).get_by_unique("John")print(user)# -> {"pk":"user_xxxx", "sk":"user_item", "name":"John", "email":"john@example.com"}pk_only=Trueoption inget_by_uniqueto getprimary keywithoutget_itempk=query.model(User).get_by_unique("John",pk_only=True)print(pk)# -> "user_xxxx"Update Itemuser=query.model(User).search(User.email.eq("john@example.com"))new_user=User(**user[0])new_user.email="new-john@example.com"query.model(new_user).update()Or use unique value to detect exist item.new_user=User(name="John",email="new-john@example.com")query.model(new_user).update()Then, tha value of "main item" and "seach item" changedpkskdatanameemaildescriptionuser_xxxxuser_itemJohnnew-john@example.comtestuser_xxxxsearch_user_nameJohnuser_xxxxsearch_user_emailnew-john@example.comDelete Itemuser = query.model(User).search(User.email.eq("new-john@example.com"))
query.model(user[0]).delete()primary keyto detect exist item.query.model(User).delete_by_pk("user_xxxx")orunique keyquery.model(User).delete_by_unique("John")Batch Writertable.batch_writer()to create/update/delete multible itemsquery.model(foo).create(batch=batch)query.model(foo).update(batch=batch)query.model(foo).delete(batch=batch)Batch Createwithtable.batch_writer()asbatch:foriinrange(3):user=User(name=f"test{i}",age=i+10)query.model(user).create(batch=batch)res=query.model(User).search(User.name.begins_with("test"))print([(r["name"],r["age"])forrinres])# -> [("test0", 10), ("test1", 11), ("test2", 12)]Batch Updatewithtable.batch_writer()asbatch:foriinrange(3):user=User(name=f"test{i}",age=i+20)query.model(user).update(batch=batch)res=query.model(User).search(User.name.begins_with("test"))print([(r["name"],r["age"])forrinres])# -> [("test0", 20), ("test1", 21), ("test2", 22)]Batch Deletepks=query.model(User).search(User.name.begins_with("test"),pk_only=True)withtable.batch_writer()asbatch:forpkinpks:query.model(user).delete_by_pk(pk,batch=batch)res=query.model(User).search(User.name.begins_with("test"))print(res)# -> []RelationshipCreate ModelYou can sat relationns to other modelsrelation=BaseModelto set relation.classBlogPost(BaseModel):__model_name__="blogpost"__table__=tablename=DBField(unique_key=True)content=DBField()author=DBField(reletion=User)Create Itemblogpost=BlogPost(name="Hello",content="Hello world",author=self.user)query.model(blogpost).create()Then, tha value "reletion item" addedpkskdatanameauthorcontentuser_xxxxuser_itemJohnuser_xxxxsearch_user_nameJohnblogpost_xxxxblogpost_itemHelloJohnHello worldblogpost_xxxxsearch_blogpost_titleHelloblogpost_xxxxrel_user_xxxxauthorIn addition to main item (sk=blogpost_item), relation item (sk=rel_{primary_key}) added to table. The GSIDataSearchIndexis used to get "relation items" to extract target's pk.
Then,batch_getitems by pk.sk = hashdata = rangepkrel_user_xxxxauthorblogpost_xxxxSearch Relationsget_relation(model=Basemodel)to search relationsblogpost=query.model(BlogPost).get_by_unique("Hello")blogpost=BlogPost(**blogpost)user=query.model(blogpost).get_relation(model=User)print(user)# -> [{"pk":"user_xxxx", "sk":"user_item", "name":"John"}]Alsoget_relation(field=DBField)to specify fielduser=query.model(blogpost).get_relation(field=BlogPost.author)print(user)# -> [{"pk":"user_xxxx", "sk":"user_item", "name":"John"}]Search ReferenceIn this library, "reference" is antonym to relationget_reference(model=Basemodel)to search items related to the itemuser=query.model(User).get_by_unique("John")user=User(**blogpost)blogpost=query.model(blogpost).get_reference(model=BlogPost)print(blogpost)# -> [{"pk":"blogpost_xxxx", "sk":"blogpost_item", "name":"Hello"}]Alsoget_reference(field=DBField)to specify fieldblogpost=query.model(user).get_reference(field=BlogPost.author)print(blogpost)# -> [{"pk":"blogpost_xxxx", "sk":"blogpost_item", "name":"Hello"}]Update RelationIf relation key's value changed, relationship also changed.new_user=User(name="Michael")blogpost=query.model(BlogPost).get_by_unique("Hello")blogpost["author"]=new_userblogpost=BlogPost(**blogpost)query.model(blogpost).update()Then, "reletion item" changedpkskdatanameauthorcontentuser_xxxxuser_itemJohnuser_xxxxsearch_user_nameJohnuser_yyyyuser_itemMichaeluser_yyyysearch_user_nameMichaelblogpost_xxxxblogpost_itemHelloMichaelHello worldblogpost_xxxxsearch_blogpost_titleHelloblogpost_xxxxrel_user_yyyyauthorDelete RelationIf related item deleted, relationship also deletedquery.model(user).delete_by_unique("Michael")Then, "reletion item" deleted.
But main item's value is not chenged.pkskdatanameauthorcontentuser_xxxxuser_itemJohnuser_xxxxsearch_user_nameJohnblogpost_xxxxblogpost_itemHelloMichaelHello worldblogpost_xxxxsearch_blogpost_titleHello