- no dependency on any particular encryption lib (there is no py stdlib
encryption lib)
- works with any modern checkpointer, ie. those which use dumps_typed
and loads_typed methods to serialize data
- uses the default msg pack serializer, but also works with any custom
serializer
- backwards compatible with unencrypted data in same storage (will just
be read unencrypted)
- providing easy constructor to use AES encryption through pycriptodome
library, one single line of code to add it in
- other encryption libraries or algorithms (even assymetric ones) can be
used by implementing the two-method CipherProtocol interface
- cipher name (eg. aes) is stored with encrypted payload for forwards
compatibility
```py
import sqlite3
from langgraph.checkpoint.serde.encrypted import EncryptedSerializer
from langgraph.checkpoint.sqlite import SqliteSaver
# will read AES key from env var LANGGRAPH_AES_KEY
serde = EncryptedSerializer.from_pycryptodome_aes()
# works with any other checkpointer, including custom ones
checkpointer = SqliteSaver(sqlite3.connect('...'), serde=serde)
```
- no dependency on any particular encryption lib (there is no py stdlib encryption lib)
- works with any modern checkpointer, ie. those which use dumps_typed and loads_typed methods to serialize data
- backwards compatible with unencrypted data in same storage (will just be read unencrypted)
- providing easy constructor to use AES encryption through pycriptodome library, one single line of code to add it in
- other encryption libraries or algorithms (even assymetric ones) can be used by implementing the two-method CipherProtocol interface
- cipher name (eg. aes) is stored with encrypted payload for forwards compatibility
- remove usage of require_at_least_one_of, we shouldn't be enforcing presence of keys in inputs/updates, an empty dict is a valid input/update
- ensure that values that were explcitly set/assigned in pydantic model are saved even if equal to default value
While working on langchain-ai/langgraphjs#984 I ported the test I was
debugging over to python so I could compare behavior. Figured I might as
well add it to this codebase, as I don't think we had this particular
case covered previously.