
    f3fi'B                    <   d Z ddlm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dlmZ dd	lmZmZmZmZmZ er	  ej0                  d
      Ze G d d             Ze G d d             Z G d de	      Z G d de      Z G d de      Zy)zPrompt caching module for LangSmith SDK.

This module provides thread-safe LRU caches with background refresh
for prompt caching. Includes both sync and async implementations.
    )annotationsN)ABC)OrderedDict)	Awaitable)	dataclass)Path)TYPE_CHECKINGAnyCallableOptionalUnionzlangsmith.cachec                  .    e Zd ZU dZded<   ded<   ddZy)	
CacheEntryz4A single cache entry with metadata for TTL tracking.r
   valuefloat
created_atc                P    |yt        j                          | j                  z
  |kD  S )z/Check if entry is past its TTL (needs refresh).F)timer   )selfttl_secondss     M/var/www/auto_recruiter/arenv/lib/python3.12/site-packages/langsmith/cache.pyis_stalezCacheEntry.is_stale"   s%    		doo-<<    N)r   Optional[float]returnbool)__name__
__module____qualname____doc____annotations__r    r   r   r   r      s    >J=r   r   c                  n    e Zd ZU dZdZded<   dZded<   dZded<   dZded<   e	dd       Z
e	dd	       Zy
)CacheMetricszCache performance metrics.r   inthitsmisses	refreshesrefresh_errorsc                4    | j                   | j                  z   S )z%Total cache requests (hits + misses).)r&   r'   r   s    r   total_requestszCacheMetrics.total_requests2   s     yy4;;&&r   c                F    | j                   }|dkD  r| j                  |z  S dS )zCache hit rate (0.0 to 1.0).r   g        )r,   r&   )r   totals     r   hit_ratezCacheMetrics.hit_rate7   s)     ##$)AItyy5 636r   N)r   r%   )r   r   )r   r   r   r    r&   r!   r'   r(   r)   propertyr,   r/   r"   r   r   r$   r$   )   sQ    $D#MFCOIsNC' ' 7 7r   r$   c                      e Zd ZdZg dZdddd	 	 	 	 	 	 	 ddZedd       Zdd	Zdd
Z	ddZ
ddZddZddZddZddZy)_BasePromptCachezBase class for prompt caches with shared LRU logic.

    Provides thread-safe in-memory LRU cache operations.
    Subclasses implement the background refresh mechanism.
    )_cache_lock	_max_size_ttl_seconds_refresh_interval_metricsd         @      N@max_sizer   refresh_interval_secondsc                   t               | _        t        j                         | _        || _        || _        || _        t               | _	        y)ag  Initialize the base cache.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale. Set to None for
                infinite TTL (entries never expire, no background refresh).
            refresh_interval_seconds: How often to check for stale entries.
        N)
r   r3   	threadingRLockr4   r5   r6   r7   r$   r8   )r   r=   r   r>   s       r   __init__z_BasePromptCache.__init__N   s<     5@M__&
!'!9$r   c                    | j                   S )zGet cache performance metrics.)r8   r+   s    r   metricsz_BasePromptCache.metricsd   s     }}r   c                "    t               | _        y)zReset all metrics to zero.N)r$   r8   r+   s    r   reset_metricsz_BasePromptCache.reset_metricsi   s    $r   c                ^   | j                   5  || j                  vr)| j                  xj                  dz  c_        	 ddd       y| j                  |   }| j                  j	                  |       | j                  xj
                  dz  c_        |j                  cddd       S # 1 sw Y   yxY w)a  Get a value from cache.

        Args:
            key: The cache key (prompt identifier like "owner/name:hash").

        Returns:
            The cached value or None if not found.
            Stale entries are still returned (background refresh handles updates).
           N)r4   r3   r8   r'   move_to_endr&   r   r   keyentrys      r   getz_BasePromptCache.getm   s     ZZ 	$++%$$)$	 	
 KK$E KK##C(MM!#;;	 	 	s   /B#AB##B,c                   | j                   5  t        j                         }t        ||      }|| j                  vrst	        | j                        | j
                  k\  rQt        t        | j                              }| j                  j                  |       t        j                  d|        || j                  |<   | j                  j                  |       ddd       y# 1 sw Y   yxY w)zSet a value in the cache.

        Args:
            key: The cache key (prompt identifier).
            value: The value to cache.
        r   r   zEvicted oldest cache entry: N)r4   r   r   r3   lenr5   nextiterpoploggerdebugrI   )r   rK   r   nowrL   
oldest_keys         r   setz_BasePromptCache.set   s     ZZ 	)))+CUs;E $++%#dkk*:dnn*L!$t{{"34

+;J<HI$DKKKK##C(	) 	) 	)s   CC##C,c                ~    | j                   5  | j                  j                  |d       ddd       y# 1 sw Y   yxY w)ziRemove a specific entry from cache.

        Args:
            key: The cache key to invalidate.
        N)r4   r3   rS   )r   rK   s     r   
invalidatez_BasePromptCache.invalidate   s2     ZZ 	'KKOOC&	' 	' 	's   3<c                z    | j                   5  | j                  j                          ddd       y# 1 sw Y   yxY w)z$Clear all cache entries from memory.N)r4   r3   clearr+   s    r   r\   z_BasePromptCache.clear   s.    ZZ 	 KK	  	  	 s   1:c                    | j                   5  | j                  j                         D cg c]"  \  }}|j                  | j                        r|$ c}}cddd       S c c}}w # 1 sw Y   yxY w)z+Get list of stale cache keys (thread-safe).N)r4   r3   itemsr   r6   rJ   s      r   _get_stale_keysz _BasePromptCache._get_stale_keys   sc    ZZ 	 #'++"3"3"5C>>$"3"34 	 		 	s   A%'AA%A%%A.c                V   ddl m} t        |      }|j                  j	                  dd       | j
                  5  i }| j                  j                         D ]  \  }}t        |j                  |j                        rNt        |j                  d      r|j                  j                  d      }n'|j                  j                         }n|j                  }|||<    d|i}d	d	d	       |j                  d
      }	 t        |d      5 }	t!        j"                  |	d       d	d	d	       |j%                  |       t&        j)                  dt+               d|        y	# 1 sw Y   ~xY w# 1 sw Y   KxY w# t,        $ r'}
|j/                         r|j1                          |
d	}
~
ww xY w)z{Dump cache contents to a JSON file for offline use.

        Args:
            path: Path to the output JSON file.
        r   schemasT)parentsexist_ok
model_dumpjson)modeentriesNz.tmpw   )indentzDumped z cache entries to )	langsmithrb   r   parentmkdirr4   r3   r^   
isinstancer   PromptCommithasattrre   dictwith_suffixopenrf   dumpreplacerT   rU   rP   	Exceptionexistsunlink)r   path
ls_schemasrh   rK   rL   
value_datadata	temp_pathfes              r   ru   z_BasePromptCache.dump   su    	4Dz$6ZZ 	(G"kk//1 *
Uekk:+B+BCu{{L9%*[[%;%;%;%H
%*[[%5%5%7
 "'J)* w'D!	(& $$V,			i% -		$!,-d#LL73w<.0B4&IJ1	( 	(*- -  	!  "G		s<   B(E =E8 	E,"=E8  E),E51E8 8	F("F##F(c           	        ddl m} t        |      }|j                         st        j                  d|        y	 t        |      5 }t        j                  |      }ddd       j                  di       }d}t        j                         }| j                  5  |j                         D ]  \  }	}
t!        | j"                        | j$                  k\  rt        j                  d|         nq	 t'        |j(                  d	      r|j(                  j+                  |
      }n|j(                  j-                  |
      }t/        ||
      }|| j"                  |	<   |dz  } ddd       t        j                  d| d|        |S # 1 sw Y   'xY w# t        j                  t        f$ r%}t        j                  d| d|        Y d}~yd}~ww xY w# t0        $ r&}t        j                  d|	 d|        Y d}~Pd}~ww xY w# 1 sw Y   xY w)a%  Load cache contents from a JSON file.

        Args:
            path: Path to the JSON file to load.

        Returns:
            Number of entries loaded.

        Loaded entries get a fresh TTL starting from load time.
        If the file doesn't exist or is corrupted, returns 0.
        r   ra   zCache file not found: NzFailed to load cache file : rh   z)Reached max cache size, stopping load at model_validaterO   rH   zFailed to load cache entry zLoaded z cache entries from )rl   rb   r   rx   rT   rU   rt   rf   loadJSONDecodeErrorOSErrorwarningrM   r   r4   r^   rP   r3   r5   rq   rp   r   	parse_objr   rw   )r   rz   r{   r   r}   r   rh   loadedrV   rK   r|   r   rL   s                r   r   z_BasePromptCache.load   s    	4Dz{{}LL1$89	d $qyy|$ ((9b)iikZZ 	#*==? Zt{{#t~~5LL#LVH!UVz668HI * 7 7 F Fz R * 7 7 A A* M 'UsCE',DKK$aKF	* 	wvh&:4&ABA$ $$$g. 	NN7vRsCD	2 ! NN%@Rs#KL#	 	sg   F FF AH-A.GHFF G(GG	G?G:4H:G??HHN)r=   r%   r   r   r>   r   r   None)r   r$   r   r   )rK   strr   zOptional[Any])rK   r   r   r
   r   r   )rK   r   r   r   )r   z	list[str])rz   Union[str, Path]r   r   )rz   r   r   r%   )r   r   r   r    	__slots__rB   r0   rD   rF   rM   rX   rZ   r\   r_   ru   r   r"   r   r   r2   r2   >   s    I '-*.' ' %	'
 #(' 
',  '.)*' 
(T5r   r2   c                  j     e Zd ZdZg dZddddd	 	 	 	 	 	 	 	 	 d fdZdd	Zdd
ZddZddZ	 xZ
S )Cachea  Thread-safe LRU cache with background thread refresh.

    For use with the synchronous Client.

    Features:
    - In-memory LRU cache with configurable max size
    - Background thread for refreshing stale entries
    - Stale-while-revalidate: returns stale data while refresh happens
    - Thread-safe for concurrent access

    Example:
        >>> def fetch_prompt(key: str) -> PromptCommit:
        ...     return client._fetch_prompt_from_api(key)
        >>> cache = PromptCache(
        ...     max_size=100,
        ...     ttl_seconds=3600,
        ...     fetch_func=fetch_prompt,
        ... )
        >>> cache.set("my-prompt:latest", prompt_commit)
        >>> cached = cache.get("my-prompt:latest")
        >>> cache.shutdown()
    )_fetch_func_shutdown_event_refresh_threadr9   r:   r;   Nr=   r   r>   
fetch_funcc                   t         |   |||       || _        t        j                         | _        d| _        | j                  | j                  | j                          yyy)a  Initialize the sync prompt cache.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale. Set to None for
                infinite TTL (offline mode - entries never expire).
            refresh_interval_seconds: How often to check for stale entries.
            fetch_func: Callback to fetch fresh data for a cache key.
                If provided, starts a background thread for refresh.
        r<   N)	superrB   r   r@   Eventr   r   r6   _start_refresh_threadr   r=   r   r>   r   	__class__s        r   rB   zCache.__init__,  so    $ 	#%= 	 	

 &(0;? 'D,=,=,I&&( -J'r   c                    | j                   j                          | j                  $| j                  j                  d       d| _        yy)zgStop background refresh thread.

        Should be called when the client is being cleaned up.
        Ng      @)timeout)r   rX   r   joinr+   s    r   shutdownzCache.shutdownL  sF    
 	  "+  %%c%2#'D  ,r   c                    t        j                  | j                  dd      | _        | j                  j	                          t
        j                  d       y)z5Start background thread for refreshing stale entries.TzPromptCache-refresh)targetdaemonnamezStarted cache refresh threadN)r@   Thread_refresh_loopr   startrT   rU   r+   s    r   r   zCache._start_refresh_threadV  sE    (//%%& 

 	""$34r   c                   | j                   j                  | j                        s8	 | j                          | j                   j                  | j                        s7yy# t        $ r"}t
        j                  d|        Y d}~Nd}~ww xY w)z)Background loop to refresh stale entries.z(Unexpected error in cache refresh loop: N)r   waitr7   _refresh_stale_entriesrw   rT   	exceptionr   r   s     r   r   zCache._refresh_loop`  sw    &&++D,B,BCQ++- &&++D,B,BC  Q  #KA3!OPPQs   A 	B
(BB
c                $   | j                   y| j                         }|syt        j                  dt	        |       d       |D ]y  }| j
                  j                         r y	 | j                  |      }| j                  ||       | j                  xj                  dz  c_	        t        j                  d|        { y# t        $ rD}| j                  xj                  dz  c_        t        j                  d| d|        Y d}~d}~ww xY w)z)Check for stale entries and refresh them.NzRefreshing  stale cache entriesrH   zRefreshed cache entry: zFailed to refresh cache entry r   )r   r_   rT   rU   rP   r   is_setrX   r8   r(   rw   r)   r   r   
stale_keysrK   	new_valuer   s        r   r   zCache._refresh_stale_entriesi  s    #))+
{3z?"33GHI 	LC##**,L ,,S1	i(''1,'6se<=	L  L,,1,!?uBqcJKKLs   %AC	D:D

D)
r=   r%   r   r   r>   r   r   zOptional[Callable[[str], Any]]r   r   r   )r   r   r   r    r   rB   r   r   r   r   __classcell__r   s   @r   r   r     sl    . FI
 '-*.59) ) %	)
 #() 3) 
)@(5QLr   r   c                  j     e Zd ZdZddgZddddd	 	 	 	 	 	 	 	 	 d fd	Zdd
ZddZddZddZ	 xZ
S )
AsyncCachea  Thread-safe LRU cache with asyncio task refresh.

    For use with the asynchronous AsyncClient.

    Features:
    - In-memory LRU cache with configurable max size
    - Asyncio task for refreshing stale entries
    - Stale-while-revalidate: returns stale data while refresh happens
    - Thread-safe for concurrent access

    Example:
        >>> async def fetch_prompt(key: str) -> PromptCommit:
        ...     return await client._afetch_prompt_from_api(key)
        >>> cache = AsyncPromptCache(
        ...     max_size=100,
        ...     ttl_seconds=3600,
        ...     fetch_func=fetch_prompt,
        ... )
        >>> await cache.start()
        >>> cache.set("my-prompt:latest", prompt_commit)
        >>> cached = cache.get("my-prompt:latest")
        >>> await cache.stop()
    r   _refresh_taskr9   r:   r;   Nr   c               F    t         |   |||       || _        d| _        y)a  Initialize the async prompt cache.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale. Set to None for
                infinite TTL (offline mode - entries never expire).
            refresh_interval_seconds: How often to check for stale entries.
            fetch_func: Async callback to fetch fresh data for a cache key.
        r<   N)r   rB   r   r   r   s        r   rB   zAsyncCache.__init__  s2    " 	#%= 	 	

 &;?r   c                   K   | j                   | j                  y| j                  yt        j                  | j                         d      | _        t        j                  d       yw)zStart async background refresh loop.

        Must be called from an async context. Creates an asyncio task that
        periodically checks for stale entries and refreshes them.
        Does nothing if ttl_seconds is None (infinite TTL mode).
        NzAsyncPromptCache-refresh)r   z Started async cache refresh task)r   r6   r   asynciocreate_taskr   rT   rU   r+   s    r   r   zAsyncCache.start  sb      #t'8'8'@)$00 +
 	78s   A'A)c                   K   | j                   y| j                   j                          	 | j                    d{    d| _         t        j                  d       y7 !# t        j                  $ r Y 4w xY ww)zlStop async background refresh loop.

        Cancels the refresh task and waits for it to complete.
        Nz Stopped async cache refresh task)r   cancelr   CancelledErrorrT   rU   r+   s    r   stopzAsyncCache.stop  sp     
 %!!#	$$$$ "78	 %%% 		s7   (A7A AA A7A A41A73A44A7c                  K   	 	 t        j                  | j                         d{    | j                          d{    A7 7 # t         j                  $ r  t
        $ r"}t        j                  d|        Y d}~>d}~ww xY ww)z/Async background loop to refresh stale entries.Nz.Unexpected error in async cache refresh loop: )r   sleepr7   r   r   rw   rT   r   r   s     r   r   zAsyncCache._refresh_loop  s     WmmD$:$:;;;11333 ;3))  W  #QRSQT!UVVWsO   B
"A	 AA	 A A	 B
A	 A	 	B%B=B
BB
c                  K   | j                   y| j                         }|syt        j                  dt	        |       d       |D ]e  }	 | j                  |       d{   }| j                  ||       | j                  xj                  dz  c_        t        j                  d|        g y7 P# t        $ rD}| j                  xj                  dz  c_	        t        j                  d| d|        Y d}~d}~ww xY ww)z8Check for stale entries and refresh them asynchronously.NzAsync refreshing r   rH   zAsync refreshed cache entry: z$Failed to async refresh cache entry r   )r   r_   rT   rU   rP   rX   r8   r(   rw   r)   r   r   s        r   r   z!AsyncCache._refresh_stale_entries  s     #))+
(Z(99MNO 		RCR"&"2"23"77	i(''1,'<SEBC		R7  R,,1,!EcU"QCPQQRsC   ADB2B0 AB2-D0B22	C?;:C:5D:C??D)
r=   r%   r   r   r>   r   r   z)Optional[Callable[[str], Awaitable[Any]]]r   r   r   )r   r   r   r    r   rB   r   r   r   r   r   r   s   @r   r   r     su    0 0I
 '-*.@D@ @ %	@
 #(@ >@ 
@29(9 
WRr   r   )r    
__future__r   r   rf   loggingr@   r   abcr   collectionsr   collections.abcr   dataclassesr   pathlibr   typingr	   r
   r   r   r   	getLoggerrT   r   r$   r2   r   r   r"   r   r   <module>r      s    #       # % !  @ @			,	- 
= 
= 
= 7 7 7(Qs QhnL nLbyR! yRr   