o
    ưiB                     @   s   d Z ddlZddlZddlZddlZddlZddlZddlZddlmZm	Z	 ddl
mZmZmZ ddlmZ ddlmZ G dd dZG d	d
 d
Z	ddeeeef  defddZdS )zs
This file contains the PrismaWrapper class, which is used to wrap the Prisma client and handle the RDS IAM token.
    N)datetime	timedelta)AnyOptionalUnion)verbose_proxy_logger)str_to_boolc                   @   s   e Zd ZdZdZdZdedefddZde	e
 d	e	e
 fd
dZde	e
 d	e	e fddZd	efddZde	e
 d	efddZd	e	e
 fddZ	d&de
de	e fddZd'ddZd'ddZd'dd Zd'd!d"Zd#e
fd$d%ZdS )(PrismaWrappera  
    Wrapper around Prisma client that handles RDS IAM token authentication.

    When iam_token_db_auth is enabled, this wrapper:
    1. Proactively refreshes IAM tokens before they expire (background task)
    2. Falls back to synchronous refresh if a token is found expired
    3. Uses proper locking to prevent race conditions during reconnection

    RDS IAM tokens are valid for 15 minutes. This wrapper refreshes them
    3 minutes before expiration to ensure uninterrupted database connectivity.
       iX  original_prismaiam_token_db_authc                 C   s&   || _ || _d | _t | _d | _d S N)_original_prismar   _token_refresh_taskasyncioLock_reconnection_lock_last_refresh_time)selfr   r    r   U/home/app/Keep/.python/lib/python3.10/site-packages/litellm/proxy/db/prisma_client.py__init__'   s
   

zPrismaWrapper.__init__db_urlreturnc                 C   sJ   |du rdS zt j|}|jrt j|jW S W dS  ty$   Y dS w )aX  
        Extract the token (password) from the DATABASE_URL.

        The token contains the AWS signature with X-Amz-Date and X-Amz-Expires parameters.

        Important: We must parse the URL while it's still encoded to preserve structure,
        then decode the password portion. Otherwise the '?' in the token breaks URL parsing.
        N)urllibparseurlparsepasswordunquote	Exception)r   r   parsedr   r   r   _extract_token_from_db_url0   s   	z(PrismaWrapper._extract_token_from_db_urltokenc           	   
   C   s   |du rdS z@d|vrW dS | ddd }tj|}|ddgd }|ddgd }|r2|s5W dS t|d}t|}|t|d W S  t	ya } zt
d	|  W Y d}~dS d}~ww )
z
        Parse the token to extract its expiration time.

        Returns the datetime when the token expires, or None if parsing fails.
        N?   zX-Amz-Expiresr   z
X-Amz-Datez%Y%m%dT%H%M%SZsecondsz"Failed to parse token expiration: )splitr   r   parse_qsgetr   strptimeintr   r   r   debug)	r   r"   Zquery_stringparamsZexpires_strZdate_strZtoken_createdZ
expires_iner   r   r   _parse_token_expirationE   s&   z%PrismaWrapper._parse_token_expirationc                 C   sn   t d}| |}| |}|du r td| j d | jS |t| jd }t	
 }||  }td|S )a  
        Calculate exactly how many seconds until we need to refresh the token.

        Uses precise timing: sleeps until (token_expiration - buffer_seconds).
        For a 15-minute (900s) token with 180s buffer, this returns ~720s (12 min).

        Returns:
            Number of seconds to sleep before the next refresh.
            Returns 0 if token should be refreshed immediately.
            Returns FALLBACK_REFRESH_INTERVAL_SECONDS if parsing fails.
        DATABASE_URLNz=Could not parse token expiration, using fallback interval of sr%   r   )osgetenvr!   r/   r   r,   !FALLBACK_REFRESH_INTERVAL_SECONDSr   TOKEN_REFRESH_BUFFER_SECONDSr   utcnowtotal_secondsmax)r   r   r"   expiration_timeZ
refresh_atnowZseconds_until_refreshr   r   r    _calculate_seconds_until_refreshd   s    



z.PrismaWrapper._calculate_seconds_until_refresh	token_urlc                 C   sB   |du rdS |  |}| |}|du rtd dS t |kS )z/Check if the token in the given URL is expired.NTz5Could not parse token expiration, treating as expired)r!   r/   r   r,   r   r6   )r   r<   r"   r9   r   r   r   is_token_expired   s   

zPrismaWrapper.is_token_expiredc           	   
   C   s   | j rJddlm} td}td}td}td}td}||||d}d	| d
| d| d
| d| 
}|rC|d| 7 }|tjd< |S dS )z5Generate a new RDS IAM token and update DATABASE_URL.r   )generate_iam_auth_tokenZDATABASE_HOSTZDATABASE_PORTZDATABASE_USERZDATABASE_NAMEZDATABASE_SCHEMA)db_hostdb_portdb_userzpostgresql://:@/z?schema=r0   N)r   Z litellm.proxy.auth.rds_iam_tokenr>   r2   r3   environ)	r   r>   r?   r@   rA   Zdb_nameZ	db_schemar"   Z_db_urlr   r   r   get_rds_iam_token   s    




"
zPrismaWrapper.get_rds_iam_tokenN
new_db_urlhttp_clientc              
      s   ddl m} z
| j I dH  W n ty+ } ztd|  W Y d}~nd}~ww |dur7||d| _n| | _| j I dH  dS )zCDisconnect and reconnect the Prisma client with a new database URL.r   )PrismaNz$Failed to disconnect Prisma client: )http)prismarI   r   Z
disconnectr   r   warningconnect)r   rG   rH   rI   r.   r   r   r   recreate_prisma_client   s   z$PrismaWrapper.recreate_prisma_clientc                    sL   | j std dS | jdurtd dS t|  | _td dS )a  
        Start the background token refresh task.

        This task proactively refreshes RDS IAM tokens before they expire,
        preventing connection failures. Should be called after the initial
        Prisma client connection is established.
        z7IAM token auth not enabled, skipping token refresh taskNz"Token refresh task already runningz7Started RDS IAM token proactive refresh background task)r   r   r,   r   r   create_task_token_refresh_loopinfor   r   r   r   start_token_refresh_task   s   

z&PrismaWrapper.start_token_refresh_taskc                    sT   | j du rdS | j   z| j I dH  W n
 tjy   Y nw d| _ td dS )z
        Stop the background token refresh task gracefully.

        Should be called during application shutdown to clean up resources.
        Nz-Stopped RDS IAM token refresh background task)r   cancelr   CancelledErrorr   rQ   rR   r   r   r   stop_token_refresh_task   s   

z%PrismaWrapper.stop_token_refresh_taskc                    s  t d| j d 	 z.|  }|dkr-t d|dd|d d	d
 t|I dH  t d |  I dH  W nJ tjyJ   t d Y dS  ty } z/t 	d| d| j
 d zt| j
I dH  W n tjyy   Y W Y d}~dS w W Y d}~nd}~ww q)aU  
        Background loop that proactively refreshes RDS IAM tokens before expiration.

        Uses precise timing: calculates the exact sleep duration until the token
        needs to be refreshed (expiration - 3 minute buffer), then refreshes.
        This is more efficient than polling, requiring only 1 wake-up per token cycle.
        z=RDS IAM token refresh loop started. Tokens will be refreshed zs before expiration.Tr   z#RDS IAM token refresh scheduled in z.0fz
 seconds (<   z.1fz	 minutes)Nz'Proactively refreshing RDS IAM token...z$RDS IAM token refresh loop cancelledz%Error in RDS IAM token refresh loop: z. Retrying in zs...)r   rQ   r5   r;   r   sleep_safe_refresh_tokenrU   r   errorr4   )r   Zsleep_secondsr.   r   r   r   rP      sH   


z!PrismaWrapper._token_refresh_loopc              	      s   | j 4 I dH 5 |  }|r"| |I dH  t | _td ntd W d  I dH  dS W d  I dH  dS 1 I dH sCw   Y  dS )z
        Refresh the RDS IAM token with proper locking to prevent race conditions.

        Uses an asyncio lock to ensure only one refresh operation happens at a time,
        preventing multiple concurrent reconnection attempts.
        NzFRDS IAM token refreshed successfully. New token valid for ~15 minutes.z=Failed to generate new RDS IAM token during proactive refresh)	r   rF   rN   r   r6   r   r   rQ   rZ   )r   rG   r   r   r   rY     s   
.z!PrismaWrapper._safe_refresh_tokennamec              
   C   s   t | j|}| jrftd}| |rftd |  }|rbt	
 }| rRt	| ||}z|jdd td W n tyQ } z	td|   d}~ww t	| | t | j|}|S td|S )	a  
        Proxy attribute access to the underlying Prisma client.

        If IAM token auth is enabled and the token is expired, this method
        provides a synchronous fallback to refresh the token. However, this
        should rarely be needed since the background task proactively refreshes
        tokens before they expire.

        FIXED: Now properly waits for reconnection to complete before returning,
        instead of the previous fire-and-forget pattern that caused the bug.
        r0   ztRDS IAM token expired in __getattr__ - proactive refresh may have failed. Triggering synchronous fallback refresh...   )timeoutz0Synchronous token refresh completed successfullyz'Failed to refresh token synchronously: NzFailed to get RDS IAM token)getattrr   r   r2   r3   r=   r   rL   rF   r   get_event_loop
is_runningrun_coroutine_threadsaferN   resultrQ   r   rZ   run
ValueError)r   r[   Zoriginal_attrr   rG   loopfuturer.   r   r   r   __getattr__)  s>   


zPrismaWrapper.__getattr__r   )r   N)__name__
__module____qualname____doc__r5   r4   r   boolr   r   strr!   r   r/   floatr;   r=   rF   rN   rS   rV   rP   rY   rg   r   r   r   r   r	      s*    	$




+r	   c                   @   s6   e Zd ZedefddZed	dedefddZdS )
PrismaManagerr   c                  C   s$   t jt} t jt j| }|S )z(Get the path to the migrations directory)r2   pathabspath__file__dirname)rq   dnamer   r   r   _get_prisma_dirb  s   zPrismaManager._get_prisma_dirFuse_migratec                 C   s  t dD ]}t }t }t| zzV| rWzddlm} W n$ tyC } zt	
d| d W Y d}~W W t|  dS d}~ww t }|j| dW W t|   S tjg d	d
dd W W t|  dS  tjy   t	d|d  d ttdd Y n7 tjy } z*d| }|dkrd| dnd}t	d| d|  ttdd W Y d}~nd}~ww W t| qt| w dS )z
        Set up the database using either prisma migrate or prisma db push

        Returns:
            bool: True if setup was successful, False otherwise
           r   )ProxyExtrasDBManagerz3[1;31mLiteLLM: Failed to import proxy extras. Got z[0mNF)rv   )rK   dbpushz--accept-data-lossrW   T)r]   checkzAttempt r$   z
 timed out         z Retrying... (z attempts left) z(The process failed to execute. Details: .)ranger2   getcwdro   ru   chdirZlitellm_proxy_extras.utilsrx   ImportErrorr   rZ   setup_database
subprocessrc   TimeoutExpiredrL   timerX   random	randrangeCalledProcessError)rv   attemptZoriginal_dirZ
prisma_dirrx   r.   Zattempts_leftZ	retry_msgr   r   r   r   i  sX   	

zPrismaManager.setup_databaseN)F)rh   ri   rj   staticmethodrm   ru   rl   r   r   r   r   r   ro   a  s
    ro   disable_updatesr   c                 C   s0   | du r
t dd} t| trt| } t|  S )ao  
    Determines if Prisma Schema updates should be applied during startup.

    Args:
        disable_updates: Controls whether schema updates are disabled.
            Accepts boolean or string ('true'/'false'). Defaults to checking DISABLE_SCHEMA_UPDATE env var.

    Returns:
        bool: True if schema updates should be applied, False if updates are disabled.

    Examples:
        >>> should_update_prisma_schema()  # Checks DISABLE_SCHEMA_UPDATE env var
        >>> should_update_prisma_schema(True)  # Explicitly disable updates
        >>> should_update_prisma_schema("false")  # Enable updates using string
    NZDISABLE_SCHEMA_UPDATEfalse)r2   r3   
isinstancerm   r   rl   )r   r   r   r   should_update_prisma_schema  s
   

r   r   )rk   r   r2   r   r   r   r   urllib.parser   r   typingr   r   r   Zlitellm._loggingr   Zlitellm.secret_managers.mainr   r	   ro   rl   rm   r   r   r   r   r   <module>   s,      P>