o
    ưiv_                     @   s   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 d dlm	Z	 d dl
mZ d dlmZ dee defdd	Zdefd
dZdefddZG dd dZdS )    N)datetime)Path)Optional)loggervaluereturnc                 C   s   | d u rdS |   dv S )NF)true1tyyes)lower)r    r   Q/home/app/Keep/.python/lib/python3.10/site-packages/litellm_proxy_extras/utils.pystr_to_bool   s   r   c                  C   s4   t j } tt drd| d< t dd| d< | S )zJGet environment variables for Prisma, handling offline mode if configured.PRISMA_OFFLINE_MODEr   ZNPM_CONFIG_PREFER_OFFLINEZNPM_CONFIG_CACHEz/app/.cache/npm)osenvironcopyr   getenv)
prisma_envr   r   r   _get_prisma_env   s   
r   c                  C   sv   t tdr9d} td}|r tj|r td|  |S tj| r0td|   | S td|  d dS )	zHGet the Prisma command to use, bypassing Python wrapper in offline mode.r   z;/app/.cache/prisma-python/binaries/node_modules/.bin/prismaZPRISMA_CLI_PATHzUsing custom Prisma CLI at zUsing cached Prisma CLI at zPrisma CLI not found at z8. Falling back to Python wrapper (may attempt downloads)Zprisma)r   r   r   pathexistsr   infowarning)Zdefault_cli_pathZcustom_cli_pathr   r   r   _get_prisma_command!   s   

r   c                   @   s   e Zd ZedefddZededefddZededefdd	Z	ed
efddZ
ed
efddZededefddZededefddZe	ddededefddZeddedefddZdS )ProxyExtrasDBManagerr   c                  C   s   t d} t jt}| rHt j| r@t |D ]%}t j||}t j| |}t j|r7t	j
||dd qt	|| q| S t	
||  | S |S )z
        Get the path to the migrations directory

        Set os.environ["LITELLM_MIGRATION_DIR"] to a custom migrations directory, to support baselining db in read-only fs.
        ZLITELLM_MIGRATION_DIRT)dirs_exist_ok)r   r   r   dirname__file__r   listdirjoinisdirshutilcopytreecopy2)Zcustom_migrations_dirZpkg_migrations_diritemZsrc_pathZdst_pathr   r   r   _get_prisma_dir=   s   
z$ProxyExtrasDBManager._get_prisma_dirschema_pathc                 C   s  t  }t|}|d d }|jddd td}|s#td dS t }z5t	d |d	 }t
jt d
ddd|dgt|ddd|d t	d t
jt d
dddgdd|d W dS  t
jyk   td Y dS  t
jy } ztd| d|j d|j  |d}~ww )z4Create a baseline migration for an existing database
migrationsZ0_initTparentsexist_okDATABASE_URLDATABASE_URL not setFz Generating baseline migration...migration.sqlmigratediffz--from-emptyz--to-url--scriptw   )stdoutchecktimeoutenvz(Marking baseline migration as applied...resolve	--applied)r7   r8   r9   z=Migration timed out - the database might be under heavy load.z#Error creating baseline migration: z, N)r   r(   r   mkdirr   r   r   errorr   r   
subprocessrunr   openTimeoutExpiredr   CalledProcessErrorstderrr6   )r)   Z
prisma_dirZprisma_dir_pathZinit_dirdatabase_urlr   Zmigration_fileer   r   r   _create_baseline_migrationX   sf   


	
z/ProxyExtrasDBManager._create_baseline_migrationmigrations_dirc                 C   s8   t  |  d}tdt| d|   dd |D S )z<Get all migration directory names from the migrations folderz/migrations/*/migration.sqlzFound z migrations at c                 S   s   g | ]}t |jjqS r   )r   parentname).0pr   r   r   
<listcomp>   s    z=ProxyExtrasDBManager._get_migration_names.<locals>.<listcomp>)globr   r   len)rG   Zmigration_pathsr   r   r   _get_migration_names   s   z)ProxyExtrasDBManager._get_migration_namesmigration_namec                 C   *   t  }tjt ddd| gddd|d dS )z(Mark a specific migration as rolled backr1   r:   --rolled-back<   Tr8   r7   capture_outputr9   Nr   r>   r?   r   rP   r   r   r   r   _roll_back_migration   s   
z)ProxyExtrasDBManager._roll_back_migrationc                 C   rQ   )z$Mark a specific migration as appliedr1   r:   r;   rS   TrT   NrV   rW   r   r   r   _resolve_specific_migration   s   
z0ProxyExtrasDBManager._resolve_specific_migrationerror_messagec                 C   ,   g d}|D ]}t || t jr dS qdS )aw  
        Check if the error message indicates a database permission error.

        Permission errors should NOT be marked as applied, as the migration
        did not actually execute successfully.

        Args:
            error_message: The error message from Prisma migrate

        Returns:
            bool: True if this is a permission error, False otherwise
        )zDatabase error code: 42501zmust be owner of tablezpermission denied for schemazpermission denied for tablezmust be owner of schemaTFresearch
IGNORECASE)rZ   Zpermission_patternspatternr   r   r   _is_permission_error   s   z)ProxyExtrasDBManager._is_permission_errorc                 C   r[   )a  
        Check if the error message indicates an idempotent operation error.

        Idempotent errors (like "column already exists") mean the migration
        has effectively already been applied, so it's safe to mark as applied.

        Args:
            error_message: The error message from Prisma migrate

        Returns:
            bool: True if this is an idempotent error, False otherwise
        )zalready existszcolumn .* already existszduplicate key value violateszrelation .* already existszconstraint .* already existszdoes not existz.Can't drop database.* because it doesn't existTFr\   )rZ   Zidempotent_patternsr`   r   r   r   _is_idempotent_error   s   
z)ProxyExtrasDBManager._is_idempotent_errorTmark_all_appliedc                 C   s  t d}|std dS t| d t d d }z	|jddd W n$ t	yK } zd	t
|v rEtd
| d W Y d}~dS |d}~ww |d }z0td t|d}tjt ddd|d|dgdd|t d W d   n1 szw   Y  W n* tjy } ztd|j  W Y d}~nd}~w tjy   td Y nw | std dS td|  z+td tjt dddt
|d|gddddt d}td |j  td! W n, tjy } ztd"|j  W Y d}~nd}~w tjy   td# Y nw |sdS t| }	td$t|	 d% |	D ]N}
z%td&|
  tjt dd'd(|
gddddt d td)|
  W q- tjy{ } zd*|jvrptd+|
 d,|j  W Y d}~q-d}~ww dS )-z
        1. Compare the current database state to schema.prisma and generate a migration for the diff.
        2. Run prisma migrate deploy to apply any pending migrations.
        3. Mark all existing migrations as applied.
        r.   r/   Nr*   z%Y%m%d%H%M%SZ_baseline_diffTr+   zPermission deniedzPermission denied - zt
unable to baseline db. Set LITELLM_MIGRATION_DIR environment variable to a writable directory to enable migrations.r0   z9Generating migration diff between DB and schema.prisma...r4   r1   r2   z
--from-urlz--to-schema-datamodelr3   rS   )r7   r8   r6   r9   z#Failed to generate migration diff: z$Migration diff generation timed out.zMigration diff was not createdzMigration diff created at z8Running prisma db execute to apply the migration diff...dbexecutez--filez--schemar8   r7   rU   textr9   zprisma db execute stdout: u'   ✅ Migration diff applied successfullyz Failed to apply migration diff: z%Migration diff application timed out.z
Resolving z migrationszResolving migration: r:   r;   zResolved migration: z/is already recorded as applied in the database.zFailed to resolve migration z: )r   r   r   r=   r   r   nowstrftimer<   	Exceptionstrr   r   r@   r>   r?   r   r   rB   rC   rA   r   r6   r   rO   rN   debug)rG   r)   rc   rD   Zdiff_dirrE   Zdiff_sql_pathfresultZmigration_namesrP   r   r   r   _resolve_all_migrations   s   
	





	
z,ProxyExtrasDBManager._resolve_all_migrationsFuse_migratec                 C   s  t  d }tdD ];}t }t  }t| z%z| rtd z;tj	t
 ddgddddt d}td	|j  td
 td t j||dd td W W W t|  dS  tjy } zYtd|j d|j  d|jv rtd|j}|r|d}t |jrtd| d t | t | td| d W Y d}~W W t|  dS td| d tj	t
 ddd|gddddt d td| d nd|jv rd|jv rtd  t | td! t || td" W Y d}~W W t|  dS d#|jv rt |jrytd$|j}|r3|dnd%}	td&|	 d'|j  |rpzt |	 td|	 d( W n tyo }
 ztd)|
  W Y d}
~
nd}
~
ww td*|	 d+|t |jrtd, td$|j}|r|d}	td-|	  t |	 td.|	 d/ t |	 td0 n
td1|j   W Y d}~nd}~ww tj	t
 d2d3d4gddd5 W W t|  dS W nW tjy   td6|d  d7 tt d8d9 Y n9 tjy9 } z+d:| }|d;krd<| d=nd>}td?| d@|  tt d8d9 W Y d}~nd}~ww W t| q
t| w dS )Aa}  
        Set up the database using either prisma migrate or prisma db push
        Uses migrations from litellm-proxy-extras package

        Args:
            schema_path (str): Path to the Prisma schema file
            use_migrate (bool): Whether to use prisma migrate instead of db push

        Returns:
            bool: True if setup was successful, False otherwise
        z/schema.prisma   zRunning prisma migrate deployr1   ZdeployrS   Trf   zprisma migrate deploy stdout: zprisma migrate deploy completedz&Running post-migration sanity check...F)rc   u)   ✅ Post-migration sanity check completedzprisma db error: z, e: ZP3009z`(\d+_.*)` migration   z
Migration zS failed due to idempotent error (e.g., column already exists), resolving as appliedu   ✅ Migration z
 resolved.NzFound failed migration: z, marking as rolled backr:   rR   z" marked as rolled back... retryingZP3005zdatabase schema is not emptya  Database schema is not empty, creating baseline migration. In read-only file system, please set an environment variable `LITELLM_MIGRATION_DIR` to a writable directory to enable migrations. Learn more - https://docs.litellm.ai/docs/proxy/prod#read-only-file-systemz4Baseline migration created, resolving all migrationsu   ✅ All migrations resolved.ZP3018zMigration name: (\d+_.*)unknownu   ❌ Migration zW failed due to insufficient permissions. Please check database user privileges. Error: z marked as rolled backz)Failed to mark migration as rolled back: z4Migration failed due to permission error. Migration zH was NOT applied. Please grant necessary database permissions and retry.z\Migration failed due to idempotent error (e.g., column already exists), resolving as appliedzRolling back migration zResolving migration z+ that failed due to existing schema objectsu   ✅ Migration resolved.zYP3018 error encountered but could not classify as permission or idempotent error. Error: rd   pushz--accept-data-loss)r8   r7   zAttempt z
 timed out         r   z Retrying... (z attempts left) z(The process failed to execute. Details: .)!r   r(   ranger   getcwdchdirr   r   r>   r?   r   r   r6   ro   rB   rC   r]   r^   grouprb   rX   rY   rF   ra   r=   rj   r   RuntimeErrorrA   timesleeprandom	randrange)rp   r)   attemptZoriginal_dirrG   rn   rE   Zmigration_matchZfailed_migrationrP   Zrollback_errorZattempts_leftZ	retry_msgr   r   r   setup_databasej  sr  





 
 



 
 




_




  	




z#ProxyExtrasDBManager.setup_databaseN)T)F)__name__
__module____qualname__staticmethodrk   r(   boolrF   listrO   rX   rY   ra   rb   ro   r   r   r   r   r   r   <   s4    ?sr   )rM   r   r   r]   r$   r>   r   r   pathlibr   typingr   Zlitellm_proxy_extras._loggingr   rk   r   r   dictr   r   r   r   r   r   r   <module>   s    