ó
    ýÞ j†?  ã                  óN  • S r SSKJr  SSKrSSKrSSKrSSKrSSKJr  \R                  " S\R                  S9r\R                  " S\R                  S9r " S S	\5      r\R                   " S
S9SS j5       r " S S\5      rSS jr " S S5      r " S S5      r " S S5      rg)a^  Custom encryption support for LangGraph.

.. warning::
    This API is in beta and may change in future versions.

This module provides a framework for implementing custom at-rest encryption
in LangGraph applications. Similar to the Auth system, it allows developers
to define custom encryption and decryption handlers that are executed
server-side.
é    )ÚannotationsN)ÚtypesÚ_BlobDecryptorT)ÚboundÚ_JsonDecryptorTc                  ó   • \ rS rSrSrSrg)ÚLangGraphBetaWarningé   z+Warning for beta features in LangGraph SDK.© N©Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__static_attributes__r   ó    Ú[/var/www/html/gaurav/venv/lib/python3.13/site-packages/langgraph_sdk/encryption/__init__.pyr	   r	      s   † Ü5r   r	   é   )Úmaxsizec                 ó8   • [         R                  " S[        SS9  g )Nz@The Encryption API is in beta and may change in future versions.é   )Ú
stacklevel)ÚwarningsÚwarnr	   r   r   r   Ú_warn_encryption_betar      s   € ä‡M‚MØJÜØór   c                  ó   • \ rS rSrSrSrg)ÚDuplicateHandlerErroré&   zMRaised when attempting to register a duplicate encryption/decryption handler.r   Nr   r   r   r   r   r   &   s   † ÙWâr   r   c                ó”  • [         R                  " U 5      (       d  [        U S[        U 5       35      e[         R                  " U 5      nUR
                  R                  5        Vs/ sH-  nUR                  UR                  UR                  4;   d  M+  UPM/     nn[        U5      S:w  a  [        U S[        U5       35      egs  snf )a	  Validate that a handler function has the correct signature.

Args:
    fn: The handler function to validate
    handler_type: Description of the handler for error messages

Raises:
    TypeError: If the handler is not an async function or has wrong parameter count
z  must be an async function, got é   z3 must accept exactly 2 parameters (ctx, data), got N)ÚinspectÚiscoroutinefunctionÚ	TypeErrorÚtypeÚ	signatureÚ
parametersÚvaluesÚkindÚPOSITIONAL_ONLYÚPOSITIONAL_OR_KEYWORDÚlen)ÚfnÚhandler_typeÚsigÚpÚparamss        r   Ú_validate_handlerr2   ,   sÉ   € ô ×&Ò& r×*Ñ*Ü˜<˜.Ð(HÌÈbËÈ
ÐSÓTÐTä
×
Ò
˜BÓ
€Cð —‘×&Ñ&Ô(óá(ˆAØ�6‰6�a×'Ñ'¨×)@Ñ)@ÐAÑA÷ 	
Ù(ð ð ô
 ˆ6ƒ{�aÓÜØˆnð  Ü # F£˜}ð.ó
ð 	
ð ùòs   Á')CÂCc                  ó6   • \ rS rSrSrSS jrS	S jrS
S jrSrg)Ú_EncryptDecoratorséF   z•Decorators for encryption handlers.

Provides @encryption.encrypt.blob and @encryption.encrypt.json decorators for
registering encryption functions.
c                ó   • Xl         g ©N©Ú_parent©ÚselfÚparents     r   Ú__init__Ú_EncryptDecorators.__init__M   ó   € Ø�r   c                ó‚   • U R                   R                  b  [        S5      e[        US5        XR                   l        U$ )a"  Register a blob encryption handler.

The handler will be called to encrypt opaque data like checkpoint blobs.

Example:
    ```python
    @encryption.encrypt.blob
    async def encrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes:
        # Encrypt the blob using your encryption service
        return encrypted_blob
    ```

Args:
    fn: The encryption handler function

Returns:
    The registered handler function

Raises:
    DuplicateHandlerError: If blob encryptor already registered
    TypeError: If handler has invalid signature
z!Blob encryptor already registeredzBlob encryptor)r9   Ú_blob_encryptorr   r2   ©r;   r-   s     r   ÚblobÚ_EncryptDecorators.blobP   ó:   € ð. �<‰<×'Ñ'Ñ3Ü'Ð(KÓLÐLÜ˜"Ð.Ô/Ø')�‰Ô$Øˆ	r   c                ó‚   • U R                   R                  b  [        S5      e[        US5        XR                   l        U$ )a¾  Register the JSON encryption handler.

Example:
    ```python
    @encryption.encrypt.json
    async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
        # Encrypt the data
        return encrypt_data(data)
    ```

Args:
    fn: The encryption handler function

Returns:
    The registered handler function

Raises:
    DuplicateHandlerError: If JSON encryptor already registered
    TypeError: If handler has invalid signature
z!JSON encryptor already registeredzJSON encryptor)r9   Ú_json_encryptorr   r2   rB   s     r   ÚjsonÚ_EncryptDecorators.jsonm   s:   € ð* �<‰<×'Ñ'Ñ3Ü'Ð(KÓLÐLÜ˜"Ð.Ô/Ø')�‰Ô$Øˆ	r   r8   N©r<   Ú
Encryption)r-   útypes.BlobEncryptorÚreturnrL   )r-   útypes.JsonEncryptorrM   rN   ©	r   r   r   r   r   r=   rC   rH   r   r   r   r   r4   r4   F   s   † ñôô÷:r   r4   c                  ó6   • \ rS rSrSrSS jrS	S jrS
S jrSrg)Ú_DecryptDecoratorsé‰   z•Decorators for decryption handlers.

Provides @encryption.decrypt.blob and @encryption.decrypt.json decorators for
registering decryption functions.
c                ó   • Xl         g r7   r8   r:   s     r   r=   Ú_DecryptDecorators.__init__�   r?   r   c                ó‚   • U R                   R                  b  [        S5      e[        US5        XR                   l        U$ )aG  Register a blob decryption handler.

The handler will be called to decrypt opaque data like checkpoint blobs.

Example:
    ```python
    @encryption.decrypt.blob
    async def decrypt_blob(
        ctx: EncryptionContext, blob: bytes
    ) -> bytes | DecryptResult[bytes]:
        # Decrypt the blob using your encryption service
        return decrypted_blob
    ```

Args:
    fn: The decryption handler function

Returns:
    The registered handler function

Raises:
    DuplicateHandlerError: If blob decryptor already registered
    TypeError: If handler has invalid signature
z!Blob decryptor already registeredzBlob decryptor)r9   Ú_blob_decryptorr   r2   rB   s     r   rC   Ú_DecryptDecorators.blob“   s:   € ð2 �<‰<×'Ñ'Ñ3Ü'Ð(KÓLÐLÜ˜"Ð.Ô/Ø')�‰Ô$Øˆ	r   c                ó‚   • U R                   R                  b  [        S5      e[        US5        XR                   l        U$ )aâ  Register the JSON decryption handler.

Example:
    ```python
    @encryption.decrypt.json
    async def decrypt_json(
        ctx: EncryptionContext, data: dict
    ) -> dict | DecryptResult[dict]:
        # Decrypt the data
        return decrypt_data(data)
    ```

Args:
    fn: The decryption handler function

Returns:
    The registered handler function

Raises:
    DuplicateHandlerError: If JSON decryptor already registered
    TypeError: If handler has invalid signature
z!JSON decryptor already registeredzJSON decryptor)r9   Ú_json_decryptorr   r2   rB   s     r   rH   Ú_DecryptDecorators.json²   rE   r   r8   NrJ   )r-   r   rM   r   )r-   r   rM   r   rO   r   r   r   rQ   rQ   ‰   s   † ñôô÷>r   rQ   c                  ól   • \ rS rSrSrSr\r S
S jrSS jr S   SS jjr	 S   SS jjr
SS	 jrSrg)rK   éÐ   už  Add custom at-rest encryption to your LangGraph application.

.. warning::
    This API is in beta and may change in future versions.

The Encryption class provides a system for implementing custom encryption
of data at rest in LangGraph applications. It supports encryption of
both opaque blobs (like checkpoints) and structured JSON data (like
metadata, context, kwargs, values, etc.).

To use, create a separate Python file and add the path to the file to your
LangGraph API configuration file (`langgraph.json`). Within that file, create
an instance of the Encryption class and register encryption and decryption
handlers as needed.

Example `langgraph.json` file:

```json
{
  "dependencies": ["."],
  "graphs": {
    "agent": "./my_agent/agent.py:graph"
  },
  "env": ".env",
  "encryption": {
    "path": "./encryption.py:my_encryption"
  }
}
```

Then the LangGraph server will load your encryption file and use it to
encrypt/decrypt data at rest.

!!! warning "JSON Encryptors Must Preserve Keys"

    JSON encryptors **must not add or remove keys** from the input dict.
    Only values may be transformed. This constraint is **enforced at runtime
    by the server** and exists because SQL JSONB merge operations (used for
    partial updates) work at the key level.

    **Correct (per-key encryption):**
    ```python
    # Input:  {"secret": "value", "plain": "x"}
    # Output: {"secret": "<encrypted>", "plain": "x"}  âœ“ Keys preserved
    ```

    **Incorrect (key consolidation):**
    ```python
    # Input:  {"secret": "value", "plain": "x"}
    # Output: {"__encrypted__": "<blob>", "plain": "x"}  âœ— Key changed
    ```

    If your encryptor needs to store auxiliary data (DEK, IV, etc.), embed it
    within the encrypted value itself, not as separate keys.

???+ example "Basic Usage"

    ```python
    from langgraph_sdk import Encryption, EncryptionContext

    my_encryption = Encryption()

    SKIP_FIELDS = {"tenant_id", "owner", "thread_id", "assistant_id"}
    ENCRYPTED_PREFIX = "encrypted:"

    @my_encryption.encrypt.blob
    async def encrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes:
        return your_encrypt_bytes(blob)

    @my_encryption.decrypt.blob
    async def decrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes:
        return your_decrypt_bytes(blob)

    @my_encryption.encrypt.json
    async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
        result = {}
        for k, v in data.items():
            if k in SKIP_FIELDS or v is None:
                result[k] = v
            else:
                result[k] = ENCRYPTED_PREFIX + your_encrypt_string(v)
        return result

    @my_encryption.decrypt.json
    async def decrypt_json(ctx: EncryptionContext, data: dict) -> dict:
        result = {}
        for k, v in data.items():
            if isinstance(v, str) and v.startswith(ENCRYPTED_PREFIX):
                result[k] = your_decrypt_string(v[len(ENCRYPTED_PREFIX):])
            else:
                result[k] = v
        return result
    ```

???+ example "Field-Specific Logic"

    The `ctx.model` and `ctx.field` attributes tell you which model type and
    specific field is being encrypted, allowing different logic:

    ```python
    @my_encryption.encrypt.json
    async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
        if ctx.field == "metadata":
            # Metadata - standard encryption
            return encrypt_standard(data)
        elif ctx.field == "values":
            # Thread values - more sensitive, use stronger encryption
            return encrypt_sensitive(data)
        else:
            return encrypt_standard(data)
    ```

    !!! warning "Model/Field May Differ Between Encrypt and Decrypt"

        Data encrypted with one `(model, field)` pair is **not guaranteed**
        to be decrypted with the same pair. The server performs SQL JSONB
        merges that can move encrypted values between models (e.g., cron
        metadata â†’ run metadata). Your decryption logic must handle data
        regardless of the `ctx.model` or `ctx.field` values at decrypt time.

        **Safe:** Use `ctx.model`/`ctx.field` for logging or metrics only.

        **Safe:** Encrypt different keys based on `ctx.field`, but use a
        single decrypt handler that decrypts any value with the encrypted
        prefix (and passes through plaintext unchanged):

        ```python
        ENCRYPTED_PREFIX = "enc:"

        @my_encryption.encrypt.json
        async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
            # Encrypt different keys depending on the field
            if ctx.field == "context":
                keys_to_encrypt = {"api_key", "secret_token"}
            else:
                keys_to_encrypt = {"email", "ssn"}
            return {
                k: ENCRYPTED_PREFIX + encrypt(v) if k in keys_to_encrypt else v
                for k, v in data.items()
            }

        @my_encryption.decrypt.json
        async def decrypt_json(ctx: EncryptionContext, data: dict) -> dict:
            # Decrypt ANY value with the prefix, regardless of model/field
            return {
                k: decrypt(v[len(ENCRYPTED_PREFIX):])
                   if isinstance(v, str) and v.startswith(ENCRYPTED_PREFIX)
                   else v
                for k, v in data.items()
            }
        ```

        **Unsafe:** Using different encryption keys or algorithms based on
        `ctx.model`/`ctx.field` will cause decryption failures.
)rV   rA   Ú_context_handlerrY   rG   ÚdecryptÚencryptc                óž   • [        5         [        U 5      U l        [        U 5      U l        SU l        SU l        SU l        SU l        SU l	        g)z#Initialize the Encryption instance.N)
r   r4   r_   rQ   r^   rA   rV   rG   rY   r]   )r;   s    r   r=   ÚEncryption.__init__  sH   € äÔÜ)¨$Ó/ˆŒÜ)¨$Ó/ˆŒØ;?ˆÔØ;?ˆÔØ;?ˆÔØ;?ˆÔØ=AˆÕr   c                ó   • Xl         U$ )a'  Register a context handler to derive encryption context from auth.

The handler receives the authenticated user and current EncryptionContext,
and returns a dict that becomes ctx.metadata for encrypt/decrypt handlers.

This allows encryption context to be derived from JWT claims or other
auth-derived data instead of requiring a separate X-Encryption-Context header.

Note: The context handler is called once per request in middleware,
so ctx.model and ctx.field will be None in the handler.

Example:
    ```python
    from langgraph_sdk import Encryption, EncryptionContext
    from starlette.authentication import BaseUser

    encryption = Encryption()

    @encryption.context
    async def get_context(user: BaseUser, ctx: EncryptionContext) -> dict:
        # Derive encryption context from authenticated user
        return {
            **ctx.metadata,  # preserve X-Encryption-Context header if present
            "tenant_id": user.tenant_id,
        }
    ```

Args:
    fn: The context handler function

Returns:
    The registered handler function
)r]   rB   s     r   ÚcontextÚEncryption.contextŠ  s   € ðD !#ÔØˆ	r   Nc                ó   • U R                   $ )zÕGet the JSON encryptor.

Args:
    _model: Ignored. Kept for backwards compatibility with langgraph-api
        which passes model_type to this method.

Returns:
    The JSON encryptor, or None if not registered.
)rG   ©r;   Ú_models     r   Úget_json_encryptorÚEncryption.get_json_encryptor¯  ó   € ð ×#Ñ#Ð#r   c                ó   • U R                   $ )zÕGet the JSON decryptor.

Args:
    _model: Ignored. Kept for backwards compatibility with langgraph-api
        which passes model_type to this method.

Returns:
    The JSON decryptor, or None if not registered.
)rY   rf   s     r   Úget_json_decryptorÚEncryption.get_json_decryptor¾  rj   r   c                ó„  • / nU R                   (       a  UR                  S5        U R                  (       a  UR                  S5        U R                  (       a  UR                  S5        U R                  (       a  UR                  S5        U R
                  (       a  UR                  S5        SSR                  U5       S3$ )	NÚblob_encryptorÚblob_decryptorÚjson_encryptorÚjson_decryptorÚcontext_handlerzEncryption(handlers=[z, z]))rA   ÚappendrV   rG   rY   r]   Újoin)r;   Úhandlerss     r   Ú__repr__ÚEncryption.__repr__Í  sŽ   € ØˆØ××Ø�O‰OÐ,Ô-Ø××Ø�O‰OÐ,Ô-Ø××Ø�O‰OÐ,Ô-Ø××Ø�O‰OÐ,Ô-Ø× × Ø�O‰OÐ-Ô.Ø& t§y¡y°Ó':Ð&;¸2Ð>Ð>r   ©rM   ÚNone)r-   útypes.ContextHandlerrM   r{   r7   )rg   ú
str | NonerM   ztypes.JsonEncryptor | None)rg   r|   rM   ztypes.JsonDecryptor | None)rM   Ústr)r   r   r   r   r   Ú	__slots__r   r=   rc   rh   rl   rw   r   r   r   r   rK   rK   Ð   se   † ñZðx€Ið €Eðô	Bô#ðN "ð$àð$ð 
$õ$ð" "ð$àð$ð 
$õ$÷?r   rK   ry   )r-   ztyping.Callabler.   r}   rM   rz   )r   Ú
__future__r   Ú	functoolsr"   Útypingr   Úlanggraph_sdk.encryptionr   ÚTypeVarÚBlobDecryptorr   ÚJsonDecryptorr   ÚUserWarningr	   Ú	lru_cacher   Ú	Exceptionr   r2   r4   rQ   rK   r   r   r   Ú<module>r‰      s®   ðñ	õ #ã Û Û Û å *à—.’.Ð!2¸%×:MÑ:MÑN€Ø—.’.Ð!2¸%×:MÑ:MÑN€ô6˜;ô 6ð ×Ò˜QÑóó  ðô	˜Iô 	ô
÷4@ñ @÷FDñ D÷NI?ò I?r   