
    ^j
9                    T   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
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mZmZ d dlZdd	lmZ dd
lmZmZmZmZ ddl m!Z! ddl"m#Z#m$Z$m%Z% ddl&m'Z'm(Z( ddl)m*Z*m+Z+ ddl,m-Z-  e+j\                  e/      Z0dZ1dZ2dZ3dZ4dZ5dZ6dZ7dZ8dZ9dZ:dZ;dZ<dZ=dZ>dZ?dZ@d eAd!eAd"eAfd#ZBd$eCeDz  eAz  d"eCfd%ZEe
 G d& d'             ZFe
 G d( d)             ZGe
 G d* d+             ZH G d, d-      ZId.eAeeA   z  d/eJdz  d"eKeAef   fd0ZLd1ej                  d"eeK   fd2ZNd1ej                  d"dfd3ZO G d4 d5      ZP G d6 d7      ZQ G d8 d9      ZR G d: d;      ZSd<e'd"eAfd=ZTd<e'd eAd>eKeAef   dz  d?eKeAef   dz  d@ee!   dz  dAeCeDz  eAz  dz  dBeJdCeAd"eUeVeA   eKeAef   eKeAef   ee!   f   fdDZWd<e'dEe(dFeAdz  d"eKeAef   fdGZXddHd<e'dIeAdFeAdz  d"e(fdJZYddHd<e'dKeAdFeAdz  d"ePfdLZZdMeAdFeAdz  d"eUeAeAdz  f   fdNZ[dEe(d"eAfdOZ\ddPdQd<e'dReAdFeAdz  dSeCd"eAf
dTZ]y)U    N)ThreadPoolExecutor)contextmanager)	dataclassfield)Path)	token_hex)AnyBinaryIOCallableIteratorListLiteraloverload   )	constants)
CachedHostdelete_pool_cacheread_pool_cachesave_pool_cache)Volume)HfHubHTTPErrorSandboxCommandErrorSandboxError)HfApiJobInfo)	get_tokenlogging)parse_durationi?  z
hf-sandboxzhf-sandbox-mode	dedicatedpoolzhf-sandbox-poolzhf-sandbox-noncezpython:3.12iX  24h2   .)	COMPLETEDERRORDELETEDCANCELED   z/.hf-sbx-servera  set -e
d=/tmp/.sbx-server
if command -v wget >/dev/null 2>&1; then wget -q --header "Authorization: Bearer $SBX_DL_TOKEN" -O "$d" "$SBX_SERVER_URL"
elif command -v curl >/dev/null 2>&1; then curl -fsSL -H "Authorization: Bearer $SBX_DL_TOKEN" -o "$d" "$SBX_SERVER_URL"
else cp "$SBX_SERVER_MOUNT/sbx-server" "$d"; fi
chmod +x "$d"
unset SBX_DL_TOKEN SBX_SERVER_URL SBX_SERVER_MOUNT
exec "$d"
hf_tokennoncereturnc                     t        j                  | j                         d| j                         t        j                        j                         S )a5  Derive the per-sandbox auth token from the user's HF token and the sandbox nonce.

    Stateless: any machine holding the same HF token can recompute it from the
    nonce stored in the job's labels, so `Sandbox.connect(job_id)` needs no local state.
    The HF token itself is never sent to the sandbox.
    zhf-sandbox:)hmacnewencodehashlibsha256	hexdigest)r)   r*   s     c/var/www/ramen.bs-engineer-server.com/venv/lib/python3.12/site-packages/huggingface_hub/_sandbox.py_derive_sandbox_tokenr4   ]   s;     88HOO%UG'<'C'C'Ew~~V``bb    durationc                 Z    t        | t        t        f      rt        |       S t        |       S )zBParse a duration like 300, "300s", "10m", "2h", "1d" into seconds.)
isinstanceintfloatr   )r6   s    r3   _duration_to_secsr;   g   s%    (S%L)8}(##r5   c                       e Zd ZU dZedz  ed<   eed<   eed<   dZedz  ed<   dZe	ed<   d	Z
eed
<   ede	fd       ZdefdZy)SandboxCommandResultz?Result of a command executed in a sandbox with [`Sandbox.run`].N	exit_codestdoutstderrsignalF	timed_outr   duration_msr+   c                      | j                   dk(  S Nr   )r>   selfs    r3   okzSandboxCommandResult.oky   s    ~~""r5   c                     t        | j                        dk  r| j                  n| j                  d d dz   }d| j                   d|d| j                   dS )NP   M   z...zSandboxCommandResult(exit_code=z	, stdout=z, duration_ms=))lenr?   r>   rC   )rG   outs     r3   __repr__zSandboxCommandResult.__repr__}   sW     -3dkkSb9IE9Q00@	#P^_c_o_o^ppqrrr5   )__name__
__module____qualname____doc__r9   __annotations__strrA   rB   boolrC   propertyrH   rO    r5   r3   r=   r=   n   sb    ITzKKFC$JItK#D # #s# sr5   r=   c                       e Zd ZU dZeed<   eee   z  ed<    edd      Z	ded<   dZ
edz  ed	<   dZedz  ed
<   dZeed<   dZedz  ed<   ddZy)SandboxProcessa  A background process started in a sandbox with [`Sandbox.run`]`(..., background=True)`.

    List a sandbox's processes with [`Sandbox.processes`] and stop one with [`SandboxProcess.kill`].
    Completed processes stay in the listing until the sandbox is deleted, so `running` and
    `exit_code` tell whether a process is still alive or already exited (as of when it was listed).
    pidcmdF)reprcompareSandbox_sandboxNtagstarted_at_msTrunningr>   c                 V    | j                   j                  dd| j                          y)z:Terminate the background process (idempotent server-side).DELETEz/processes/N)r`   _requestr[   rF   s    r3   killzSandboxProcess.kill   s!    x;txxj)ABr5   r+   N)rP   rQ   rR   rS   r9   rT   rU   r   r   r`   ra   rb   rc   rV   r>   rg   rX   r5   r3   rZ   rZ      si     
H	tCy  UE:Hi:Ct $M3:$GT IsTz Cr5   rZ   c                   b    e Zd ZU dZeed<   eed<   ed   ed<   eed<   dZedz  ed<   d	Z	eed
<   y)	FileEntryz%A file or directory inside a sandbox.namepath)filedirsymlinktypesizeNmtime_ms mode)
rP   rQ   rR   rS   rU   rT   r   r9   rr   rt   rX   r5   r3   rj   rj      s7    /
I
I
*
++
IHcDjD#Nr5   rj   c            	          e Zd ZdZdZdZdZddZdede	fd	Z
d ded
edefdZd!dedee	z  ez  dedz  ddfdZd!deez  dededz  ddfdZdedeez  ddfdZdedeeeef      fdZdee   deegef   dee   fdZdededee	   fdZdede	dedz  ddfdZdedee   fdZdedefdZdedefdZd"dededdfdZdeddfdZ y)#SandboxFilesu=  Filesystem operations inside a sandbox, available as [`Sandbox.files`].

    In shared (pool) mode, paths are rooted at the sandbox's private home — the
    only place its code can write — so a leading `/` is taken relative to that
    home. In dedicated mode, paths are absolute on the container filesystem.
    i          r+   Nc                     || _         y N)r`   rG   sandboxs     r3   __init__zSandboxFiles.__init__   s	    r5   rl   c                     | j                  |      j                  }|| j                  kD  r!dj                  | j	                  ||            S | j
                  j                  ddd|i      }|j                  S )z=Read a file from the sandbox and return its content as bytes.r5   GET/files/readrl   params)statrq   PARALLEL_THRESHOLDjoin_read_rangesr`   rf   content)rG   rl   rq   responses       r3   readzSandboxFiles.read   sk    yy##$)))88D--dD9::==))%PT~)Vr5   encodingc                 B    | j                  |      j                  |      S )z@Read a file from the sandbox and return its content as a string.)r   decode)rG   rl   r   s      r3   	read_textzSandboxFiles.read_text   s    yy%%h//r5   datart   c                 2   t        |t              r|j                         }n t        |t              s|j	                         }t        |      | j                  kD  r| j                  |||       yd|i}|||d<   | j                  j                  dd||       y)zHWrite content to a file in the sandbox (parent directories are created).Nrl   rt   PUT/files/writer   r   )
r8   rU   r/   bytesr   rM   r   _write_rangesr`   rf   )rG   rl   r   rt   r   s        r3   writezSandboxFiles.write   s    dC ;;=DD%(99;Dt9t...tT40$!F6NunVTRr5   
local_pathc                 (   t        |      j                         j                  }|| j                  kD  r+| j	                  |t        |      j                         |       yt        |d      5 }| j                  |||       ddd       y# 1 sw Y   yxY w)z#Upload a local file to the sandbox.Nrb)rt   )r   r   st_sizer   r   
read_bytesopenr   )rG   r   rl   rt   rq   fs         r3   uploadzSandboxFiles.upload   s    J$$&..$)))tT*%5%@%@%BDI*d# 	+qJJtQTJ*	+ 	+ 	+s   *BBc                    | j                  |      j                  }|| j                  kD  r?t        |d      5 }| j	                  ||      D ]  }|j                  |        	 ddd       y| j                  j                  ddd|i      5 }t        |d      5 }|j                  d      D ]  }|j                  |        	 ddd       ddd       y# 1 sw Y   yxY w# 1 sw Y   xY w# 1 sw Y   yxY w)	z!Download a file from the sandbox.wbNr   r   rl   r   rw   )
chunk_size)	r   rq   r   r   r   r   r`   _stream
iter_bytes)rG   rl   r   rq   r   partr   chunks           r3   downloadzSandboxFiles.download   s    yy##$)))j$' "1 --dD9 "DGGDM"" ]]""5-"O 	#S[j$' #1%00K0H #EGGEN##	# 	#	" # #	# 	#s/   )C
C*)CC*CC'	#C**C3rq   c           	      x    | j                   }t        d||      D cg c]  }|t        |||z
        f c}S c c}w rE   )PARALLEL_CHUNK_SIZErangemin)rG   rq   r   offsets       r3   _rangeszSandboxFiles._ranges   s;    ((BG4QVBWXUD6M23XXXs   7itemsfnc                     t        | j                  t        |            }t        |      5 }t	        |j                  ||            cddd       S # 1 sw Y   yxY w)zRun `fn(item)` over items concurrently.

        All workers share the sandbox's `httpx.Client`, which is thread-safe and pools
        connections, so parallel transfers fan out over several streams at once.
        N)r   PARALLEL_MAX_WORKERSrM   r   listmap)rG   r   r   workersexecutors        r3   	_parallelzSandboxFiles._parallel   sL     d//U<( 	1HR/0	1 	1 	1s   AAc                      dt         t        t        f   dt        f fd} j                   j	                  |      |      S )Nrngr+   c                 j    | \  }}j                   j                  dd||d      }|j                  S )Nr   r   )rl   r   lengthr   )r`   rf   r   )r   r   r   r   rl   rG   s       r3   fetchz(SandboxFiles._read_ranges.<locals>.fetch   sB     NFF}}--}dfX^-_ . H ###r5   )tupler9   r   r   r   )rG   rl   rq   r   s   ``  r3   r   zSandboxFiles._read_ranges   s:    	$uS#X 	$5 	$ ~~dll40%88r5   c                      dt         t        t        f   dd f fd} j                   j                  t	                    |       y )Nr   r+   c           	      r    | \  }}|d}|d<   j                   j                  dd||||z           y )N)rl   r   rt   r   r   r   r`   rf   )r   r   r   r   r   rt   rl   rG   s       r3   pushz(SandboxFiles._write_ranges.<locals>.push  sP     NFF.2f%EF!%vMM""5.QUV\_ehn_nQo"pr5   )r   r9   r   r   rM   )rG   rl   r   rt   r   s   ```` r3   r   zSandboxFiles._write_ranges   sD    	qeCHo 	q$ 	q 	q 	t||CI.5r5   c                     | j                   j                  ddd|i      }|j                         d   D cg c]  }t        di | c}S c c}w )z List a directory in the sandbox.r   z/files/listrl   r   entriesrX   )r`   rf   jsonrj   )rG   rl   r   entrys       r3   r   zSandboxFiles.list
  sI    ==))%PT~)V08	0JKu	"E"KKKs   Ac                 t    | j                   j                  ddd|i      }t        di |j                         S )z3Get metadata of a file or directory in the sandbox.r   z/files/statrl   r   rX   )r`   rf   rj   r   )rG   rl   r   s      r3   r   zSandboxFiles.stat  s6    ==))%PT~)V+8==?++r5   c                 t    	 | j                  |       y# t        $ r}|j                  dk(  rY d}~y d}~ww xY w)z+Check whether a path exists in the sandbox.Ti  NF)r   r   status_code)rG   rl   es      r3   existszSandboxFiles.exists  s7    	IIdO 	}}#	s    	7227	recursivec                 V    d|i}|rd|d<   | j                   j                  dd|       y)z*Delete a file or directory in the sandbox.rl   1r   re   z/files/deleter   Nr   )rG   rl   r   r   s       r3   deletezSandboxFiles.delete  s1    $"%F;xHr5   c                 D    | j                   j                  ddd|i       y)z0Create a directory (and parents) in the sandbox.POSTz/files/mkdirrl   r   Nr   )rG   rl   s     r3   mkdirzSandboxFiles.mkdir%  s    v~vtnMr5   )r|   r_   r+   N)zutf-8rz   )F)!rP   rQ   rR   rS   r   r   r   r}   rU   r   r   r   r
   r   r   r   r   r9   r   r   r   r	   r   r   r   r   rj   r   r   rV   r   r   r   rX   r5   r3   rv   rv      s    ))      0c 0S 0s 0S# SS5[8%; S3: SY] S+t +3 +cDj +TX +#S #cDj #T #YC YDsCx$9 Y1tCy 1hucz.B 1tCy 19 9C 9DK 96# 6U 6#* 6 6L Li L
, , ,
3 4 I3 I4 ID IN# N$ Nr5   rv   r\   shellc                     |du rt        | t              st        d      |du rt        | t              rt        d      d| i}|||d<   |S )zQBuild the `cmd`/`shell` part of an `/exec` payload, validating their consistency.TzCshell=True requires `cmd` to be a shell command string, not a list.FzRshell=False requires `cmd` to be an argv list (e.g. ['echo', 'hi']), not a string.r\   r   )r8   rU   
ValueError)r\   r   payloads      r3   _exec_payloadr   *  sX    }ZS1^__~*S#.mnn$clG Nr5   r   c              #      K   | j                         D ]3  }|st        j                  |      }|j                  d      dk7  s0| 5 yw)zJIterate NDJSON events from a streaming response, skipping keepalive pings.eventpingN)
iter_linesr   loadsget)r   liner   s      r3   _iter_eventsr   6  sH     ##% 

4 99W'Ks   A A
A
c                     | j                          	 | j                         d   }t	        d| j
                   d| | j
                        # t        $ r | j                  dd }Y Aw xY w)zQRead the error body and raise a SandboxError (works for streaming responses too).errorNi  zSandbox API error (z): )r   )r   r   	Exceptiontextr   r   )r   messages     r3   _raise_for_statusr   @  sl    MMO&--/'* ,X-A-A,B#gYO]e]q]q
rr  &--%&s   A A'&A'c                       e Zd ZdZddddedededz  d	ed
edededededdfdZedddde	d
edededededd fd       Z
ededz  fd       Zdededej                  fdZedededeej                     fd       ZddZddZdeddfdZy)_SandboxServeruE  HTTP transport to one `sbx-server` instance — a dedicated job or a shared host.

    Owns the `httpx.Client`, the base URL and the auth headers.
    In dedicated mode a server is paired 1:1 with its [`Sandbox`
    In pool mode one server (one host job) is shared by many sandboxes, and `live`/`capacity` track packing.
    
   r   )max_connectionscapacityjob_idownerimageNbase_urlr*   sandbox_tokenapir   r   r+   c       	         4   || _         || _        || _        || _        || _        || _        t        |      | _        || _        |	| _	        d| _
        d| _        t        j                  d| j                   |dt        j                  ||      d      | _        y )Nr   TBearer AuthorizationzX-Sandbox-Token)r   max_keepalive_connections)headerslimitsfollow_redirects)r   r   _imager   r*   _api_effective_token_auth_token_sandbox_tokenr   liveverifiedhttpxClientLimits_client)
rG   r   r   r   r   r*   r   r   r   r   s
             r3   r}   z_SandboxServer.__init__R  s     
  
	+C0+ 	  ||#*4+;+;*<!=#0 <<[jk!
r5   jobc                     | |j                   |j                  j                  |j                  xs |j                  t        |      |||||	      S )zIBuild a server from a freshly fetched job (reads its exposed server URL).	r   r   r   r   r*   r   r   r   r   )idr   rk   docker_imagespace_id_find_server_url)clsr   r*   r   r   r   r   s          r3   from_jobz_SandboxServer.from_jobz  sL     66))..""2cll%c*'+

 
	
r5   c                     | j                   S rz   )r   rF   s    r3   r   z_SandboxServer.image  s    {{r5   methodrl   c                     |j                  dt        j                  dd            } | j                  j                  || j
                  |z   fd|i|}|j                  dk\  rt        |       |S )z@Request to the in-job server. Raises SandboxError on API errors.timeoutg      N@      $@connect  )popr   Timeoutr   requestr   r   r   rG   r
  rl   kwargsr  r   s         r3   r  z_SandboxServer.request  sd    **YdD(IJ'4<<''0D`g`Y_`3&h'r5   c              +     K   |j                  dt        j                  dd            } | j                  j                  || j
                  |z   fd|i|5 }|j                  dk\  rt        |       | ddd       y# 1 sw Y   yxY ww)zJStreaming request to the in-job server. Raises SandboxError on API errors.r  g     Q@r  r  r  N)r  r   r  r   streamr   r   r   r  s         r3   r  z_SandboxServer.stream  s      **YdD(IJ T\\  )=YwYRXY 	]e##s*!(+N	 	 	s   ABA?6	B?BBc                 8    | j                   j                          y rz   )r   closerF   s    r3   r  z_SandboxServer.close  s    r5   c                 f    | j                   j                  | j                  | j                         y )Nr   	namespace)r   
cancel_jobr   r   rF   s    r3   r  z_SandboxServer.cancel_job  s!    		DKK4::Fr5   start_timeoutc           
         t        j                          |z   }d}t        j                          |k  rm	 | j                  j                  | j                  dz   t	        j
                  d            }|j                  dk(  ry	 t        j                          |z
  dkD  rt        j                          }| j                  j                  | j                  | j                        }|j                  j                  t        v rut        | j                  | j                  | j                  	      }t!        d
| j                   d|j                  j                   d|j                  j"                   d|       t        j$                  d       t        j                          |k  rmt!        d
| j                   d|dd      # t        j                  $ r Y Qw xY w)zMPoll /health until the server answers; fail fast (with logs) if the job dies.g        z/healthg      @)r     Ng       @r  r  zSandbox job z$ terminated during startup (status: z, message: ).g333333?z did not become ready within z.0fzs.)timer   r   r   r   r  r   RequestErrorr   inspect_jobr   r   statusstage_TERMINAL_STAGES_tail_job_logsr   r   sleep)rG   r  deadlinelast_job_checkr   r   logss          r3   
wait_readyz_SandboxServer.wait_ready  s   99;.iikH$<<++DMMI,Eu}}]`Oa+b''3. / yy{^+c1!%ii++4;;$**+U::##'77)$))T[[DJJWD&&t{{m 4$$'JJ$4$4#5[ASAS@TTVW[V\^  JJt! iikH$" \$++6STabeSffhijj %% s   AF< <GGrh   )rP   rQ   rR   rS   rU   r   r9   r}   classmethodr   r  rW   r   r   Responser  r   r   r  r  r  r:   r.  rX   r5   r3   r   r   J  so   "  "&
 &
 	&

 Tz&
 &
 &
 &
 &
 &
 &
 
&
P   "
 
 	

 
 
 
 
 

 
. sTz  c  5>>  S  (5>>:R  Gk k$ kr5   r   c            	       v    e Zd ZdZedddeded   fd       Zedddedeg df   fd	       Zdd
deded   fdZy)_KillMethodz>Lets `kill` work both as a classmethod and an instance method.instanceNr   r+   ).Nc                      y rz   rX   rG   r3  r   s      r3   __get__z_KillMethod.__get__  s    KNr5   r_   c                      y rz   rX   r5  s      r3   r6  z_KillMethod.__get__  s    ORr5   Sandbox | Nonec           	      l    ||j                   S d d ddt        dt        d z  dt        d z  dd ffd}|S )Nr  token
sandbox_idr  r;  r+   c                J    j                  | ||      j                          y )Nr:  )r  rg   )r<  r  r;  r   s      r3   rg   z!_KillMethod.__get__.<locals>.kill  s    MM*	MGLLNr5   )_killrU   )rG   r3  r   rg   s     ` r3   r6  z_KillMethod.__get__  sP    >>!=AW[ 	OS 	Od
 	O#PT* 	O`d 	O r5   )rP   rQ   rR   rS   r   rp   r   r6  rX   r5   r3   r2  r2    sm    HNNTNhy6IN NR	R$R8BH;MR R 0  (9BU r5   r2  c                      e Zd ZdZdedededz  dededdfd	Zee	fd
e
dddddddd	dededeez  ez  dz  deeef   dz  deeef   dz  dee   dz  dedz  dedededz  dd fd       Zeddddededz  dedz  dd fd       Z e       Zd?dZd?dZd@dZd?dZed d d d d d d d d d!	d"eee   z  d#edz  deeef   dz  d$edz  d%edz  d&edz  d'eegdf   dz  d(eegdf   dz  d)ed*ed   defd+       Zed d d d,d"eee   z  d#edz  deeef   dz  d$edz  d*ed-   defd.       Zdddddddd-dd!	d"eee   z  d#edz  deeef   dz  d$edz  d%edz  d&edz  d'eegdf   dz  d(eegdf   dz  d)ed*edeez  fd/Zdee   fd0Ze dedz  fd1       Z!e dedz  fd2       Z"dAd3d4d5eez  d6ed7edefd8Z#e deeef   fd9       Z$defd:Z%d;ed<ede&jN                  fd=Z(e)d;ed<ede*e&jN                     fd>       Z+y)Br_   a  An isolated cloud machine running on Hugging Face Jobs.

    Create a dedicated one with [`Sandbox.create`] (one job per sandbox), or get many cheap shared ones from a [`SandboxPool`].
    Reattach to a running sandbox from anywhere with [`Sandbox.connect`]. Use as a context manager to terminate it on exit:

    ```python
    >>> from huggingface_hub import Sandbox
    >>> with Sandbox.create(image="python:3.12") as sbx:
    ...     print(sbx.run("python --version").stdout)
    ```
    r  serverlocal_idNowns_sandboxowns_serverr+   c                    || _         || _        || _        |dnd| | _        || _        || _        d | _        d| _        t        |       | _	        y )Nz/v1/v1/sandboxes/F)
r  _server	_local_id
_base_path_owns_sandbox_owns_server_on_kill_killedrv   files)rG   r  r@  rA  rB  rC  s         r3   r}   zSandbox.__init__  s^     ! $,#3%>(9T * (<@!$'
r5   	cpu-basicF      ^@)	flavoridle_timeoutenvsecretsvolumesr  forward_hf_tokenr  r;  r   rP  rQ  rR  rS  rT  r  rU  r  r;  c       	            t        |
      }t        |      }t        d      }t        ||      }t	        ||||||||      \  }}}}|j                  |||||t        t        dt        t        t        |i|xs dt        g|
      }d}	 t        j                  ||||t        j                  dz         }|j!                  |	        | |j&                  |ddd      S # t"        $ r 	 |j%                  |j&                  |j(                  j*                  	       n;# t"        $ r/}t,        j/                  d
|j&                   d|        Y d}~nd}~ww xY w||j1                           w xY w)u3  Create a dedicated sandbox (one HF Job) and block until it is ready (~7s on cpu-basic).

        Each sandbox is a full isolated VM, so this is the right choice for GPU
        workloads or untrusted code. To fan out many cheap CPU sandboxes instead, use
        [`SandboxPool`].

        The job runs with a fixed 24h maximum lifetime; `idle_timeout` is the real
        keeper — an idle sandbox shuts itself down well before that.

        Args:
            image (`str`, *optional*, defaults to `"python:3.12"`):
                Any Docker image with `/bin/sh` (Docker Hub or `hf.co/spaces/...`).
            flavor (`str`, *optional*, defaults to `"cpu-basic"`):
                Hardware flavor, e.g. `"cpu-basic"`, `"a10g-small"`. See `hf jobs hardware`.
            idle_timeout (`int` or `float` or `str`, *optional*, defaults to `600`):
                Auto-shutdown after this much inactivity (no API calls, no running
                processes). Defaults to 10 minutes; pass `None` to disable.
            env (`dict[str, Any]`, *optional*):
                Environment variables available in the sandbox.
            secrets (`dict[str, Any]`, *optional*):
                Secret environment variables (encrypted server-side).
            volumes (`List[Volume]`, *optional*):
                HF repos/buckets to mount, see [`Volume`].
            namespace (`str`, *optional*):
                User or org namespace to run under (defaults to current user).
            forward_hf_token (`bool`, *optional*, defaults to `False`):
                If True, your HF token is injected as `HF_TOKEN` (opt-in).
            start_timeout (`float`, *optional*, defaults to `120.0`):
                Max seconds to wait for the sandbox to become ready.
            token (`str`, *optional*):
                HF token override.

        The image only needs `/bin/sh`. The sandbox server is downloaded at startup with
        `wget`/`curl` if available, otherwise read off an always-mounted server bucket (which
        adds ~2-3s to cold start, so shipping `wget`/`curl` keeps it fast).
        r;  rx   rR  rS  rT  rQ  rU  r   r   N
r   commandrR  rS  rP  r  labelsrT  exposer     r   r*   r   r   r   r  zFailed to cancel sandbox job  after startup failure: Tr  r@  rA  rB  rC  )r   r   r   r4   _bootstrap_job_specrun_jobSANDBOX_MAX_LIFETIMESANDBOX_LABEL
MODE_LABELMODE_DEDICATEDNONCE_LABELSANDBOX_SERVER_PORTr   r  rv   r   r.  r   r  r  r   rk   loggerwarningr  )r  r   rP  rQ  rR  rS  rT  r  rU  r  r;  r   r)   r*   r   rZ  job_envjob_secretsjob_volumesr   r@  r   s                         r3   createzSandbox.create   s   f % #C("-h>5H%-'	6
2+{ kk(!3
NKQVW'4'(  
 +/	#,,+ , A AA E - F m, cffVd[_``  		dcff		G d!>svvhF^_`^abccd!		s6   <C 	E&%1DE&	E %E
E&
EE&r:  r<  c                   t        |      }t        ||      \  }}t        |v r||j                  t        d      \  }}t	        |||      }	 |j                  dd      j                         D ch c]  }|d   	 }	}||	vrt        d| d| d	      	  | |||d
d      S |j                  ||      }
|
j                  xs i }|j                  t              }|j                  t              dk7  s|t        d| dt         d      |j                  t              t         k(  rt        d| dt         d      |
j"                  j$                  dk7  r&t        d| d|
j"                  j$                   d      t'        t)        |      |      }t*        j-                  |
|||t.        j0                  dz         } | |
j2                  |dd
d      S c c}w # t        $ r |j                           w xY w)z?Reattach to a running sandbox from anywhere, using only its id.rW  r   r!  r   /v1/sandboxesr  zSandbox z no longer exists on host r#   FTr`  r  r   NJob z is not a sandbox (missing 'z	' label).z` is a sandbox host, not a single sandbox. Connect to one of its sandboxes with id '<host_job_id>z<local_id>'.RUNNING is not running (status: r"  r]  r^  )r   _split_sandbox_idSHARED_ID_SEPsplit_connect_hostr  r   r   r   r  r%  r[  r   rg  rd  re  	MODE_POOLr&  r'  r4   r   r   r  rv   r   r  )r  r<  r  r;  r   host_job_idrA  r@  itemexistingr   r[  r*   r   s                 r3   r  zSandbox.connecte  s    %  1*i H
IJ&$.$4$4]A$F!K"3yIF39>>%3Y3^3^3`a4DJaa8+&*=WXcWdde'fgg ,
 *VhUZhlmmooZ9oE!r

;'::m$+u}j\1Mm_\efgg::j!Y.zl #33@/O  ::y(*5NszzO_O_N``bcdd-.>s.CUK(('(==A ) 
 cffVd\`aa9 b  s   "G .G:G G G0c                    | j                   ry	 | j                  | j                  j                          n)| j                  j	                  dd| j                          d| _         | j                  | j                  |        | j                  r| j                  j                          yy# t
        $ r/}t        j                  d| j                   d|        Y d}~yd}~ww xY w)zTerminate the sandbox. Idempotent.

        Dedicated sandboxes cancel their underlying job; shared sandboxes are
        removed from their host (freeing a slot) while the host keeps running.
        Nre   rE  zFailed to kill sandbox : T)rL  rG  rF  r  r  r   ri  rj  r  rK  rJ  r  )rG   r   s     r3   r>  zSandbox._kill  s     <<	~~%'')$$X?O/PQ
 ==$MM$LL    	NN4TWWIRsCD	s   AB+ +	C#4%CC#c                 R    | j                   r| j                  j                          yy)zRelease the local HTTP client without terminating the sandbox. Idempotent.

        No-op for pool sandboxes (the client belongs to the pool's host).
        N)rJ  rF  r  rF   s    r3   r  zSandbox.close  s"    
 LL  r5   c                     | S rz   rX   rF   s    r3   	__enter__zSandbox.__enter__      r5   c                 ^    | j                   r| j                          y | j                          y rz   )rI  rg   r  rG   exc_infos     r3   __exit__zSandbox.__exit__  s    IIKJJLr5   .)	r   rR  cwdr  stdin	on_stdout	on_stderrcheck
backgroundr\   r   r  r  r  r  r  r  r  c       	              y rz   rX   )rG   r\   r   rR  r  r  r  r  r  r  r  s              r3   runzSandbox.run  s      #r5   )r   rR  r  Tc                     y rz   rX   )rG   r\   r   rR  r  r  s         r3   r  zSandbox.run  s     r5   c       	         N   t        ||      }|r||d<   |r||d<   |
rC| j                  dd|      j                         }t        |d   ||j	                  d      |       S |||d
<   |||d<   g }g }d	}| j                  dd|      5 }t        |      D ]  }|d   dk(  r#|j                  |d          |" ||d          .|d   dk(  r#|j                  |d          |M ||d          Y|d   dk(  sbt        |d   dj                  |      dj                  |      |j	                  d      |j	                  dd      |j	                  dd            } 	 d	d	d	       |t        d      |	r(|j                  dk7  s|j                  rt        ||      |S # 1 sw Y   BxY w)u+	  Run a command in the sandbox and wait for it, streaming output live.

        With `background=True` the command is started detached and `run` returns a
        [`SandboxProcess`] immediately, without waiting for it to finish — handy for
        servers and other long-running processes. List them later with [`Sandbox.processes`]
        and stop one with [`SandboxProcess.kill`]. The streaming/wait-only options
        (`timeout`, `stdin`, `on_stdout`, `on_stderr`, `check`) don't apply in that mode.

        Args:
            cmd (`str` or `List[str]`):
                A shell command string (run with `/bin/sh -c`) or an argv list (exec'd directly).
            shell (`bool`, *optional*):
                Force the execution mode instead of inferring it from the type of `cmd`.
                `True` runs through `/bin/sh -c` and requires `cmd` to be a string; `False`
                exec's `cmd` directly and requires it to be an argv list. `None` (default)
                infers from the type. Set it explicitly to avoid the type-driven footgun (e.g.
                `["echo hi"]` being exec'd as a single program named `"echo hi"`).
            env (`dict[str, Any]`, *optional*):
                Extra environment variables for this command.
            cwd (`str`, *optional*):
                Working directory.
            timeout (`float`, *optional*):
                Kill the command (whole process group) after this many seconds.
            stdin (`str`, *optional*):
                Data to write to the command's stdin.
            on_stdout (`Callable[[str], None]`, *optional*):
                Callback invoked with stdout chunks as they arrive.
            on_stderr (`Callable[[str], None]`, *optional*):
                Callback invoked with stderr chunks as they arrive.
            check (`bool`, *optional*, defaults to `True`):
                If True, raise [`SandboxCommandError`] on non-zero exit.
            background (`bool`, *optional*, defaults to `False`):
                If True, start the command detached and return a [`SandboxProcess`] right
                away instead of waiting for it and returning a [`SandboxCommandResult`].

        Returns: a [`SandboxCommandResult`] (with `exit_code`, `stdout`, `stderr`,
        `duration_ms`), or a [`SandboxProcess`] when `background=True`.
        rR  r  r   
/processesr   r[   ra   )r[   r\   ra   r`   Nr  r  z/execr   r?   r   r@   exitr>   rs   rA   rB   FrC   r   )r>   r?   r@   rA   rB   rC   z%connection lost while running command)r\   result)r   rf   r   rZ   r   r   r   appendr=   r   r   r>   rB   r   )rG   r\   r   rR  r  r  r  r  r  r  r  r   r   stdout_partsstderr_partsr  r   r   s                     r3   r  zSandbox.run  s   h  U+ GEN GEN==G=DIIKD!d5ksZ^__!(GI$GG"$"$.2\\&'\8 	H%h/ >X- ''f6 ,!%-07^x/ ''f6 ,!%-07^v-1"'"4!ww|4!ww|4$yy2"'))K"?$)IImQ$?F	& >FGGf&&!+v/?/?%#f==/	 	s   -F6*F!F6A#FF$c                     | j                  dd      j                         }|D cg c]J  }t        |d   |d   |j                  d      |j                  d      |d   |j                  d      | 	      L c}S c c}w )
a.  List the background processes of this sandbox.

        Returns the processes started with [`Sandbox.run`]`(..., background=True)`; stop one
        with [`SandboxProcess.kill`]. Completed processes stay listed (with `running=False` and
        their `exit_code`) until the sandbox is deleted.
        r   r  r[   r\   ra   rb   rc   r>   )r[   r\   ra   rb   rc   r>   r`   )rf   r   rZ   r   )rG   r   ps      r3   	processeszSandbox.processes2  s     }}UL1668 
  eHeHEE%LeeO4)%%,
 	
 
s   AA7c                 .    | j                   j                  S rz   )rF  r   rF   s    r3   r   zSandbox.imageI  s    ||!!!r5   c                 J    | j                   | j                  j                  S dS )zIFor a shared/pool sandbox, the job id of the host running it (else None).N)rG  rF  r   rF   s    r3   host_idzSandbox.host_idM  s"     '+nn&@t||""JdJr5   zhttps://)schemeportrl   r  c                    | j                   j                  j                  dd      d   }|j                  d      r|nd|z   }| | | j                   d| | S )a4  Public URL that proxies through to a server running *inside* this sandbox.

        Requests to the returned URL are forwarded by the in-job sandbox server to a
        server you started in the sandbox on `port`, including WebSocket (`ws(s)://`)
        upgrades and streamed responses. Pair it with [`proxy_headers`] for auth.

        How the sandbox must listen on `port`:

        - **Pool / shared sandbox**: it cannot bind a TCP port (Landlock), so bind a
          **unix socket** at `$SBX_PROXY_DIR/<port>.sock` (the `SBX_PROXY_DIR` env var
          is set in every sandbox). E.g. `uvicorn app:app --uds $SBX_PROXY_DIR/8000.sock`.
        - **Dedicated sandbox**: bind a normal TCP port on `127.0.0.1:<port>`. (You can
          also expose the port directly via the job proxy without going through here.)

        Args:
            port (`int` or `str`):
                The port (pool: the `<port>` of the unix socket) the inner server listens on.
            path (`str`, *optional*, defaults to `"/"`):
                Path on the inner server to point at, e.g. `"/ws"`.
            scheme (`str`, *optional*, defaults to `"https://"`):
                URL scheme to build the link with. Defaults to `"https://"`; pass
                `"wss://"` for a WebSocket client (the proxy is protocol-agnostic, so only
                the client-side scheme changes).

        Returns:
            `str`: a URL like `https://<job_id>--49983.hf.jobs/v1/.../proxy/8000/ws` (or
            `wss://...` with `scheme="wss://"`).

        Example:
            ```python
            >>> url = sandbox.proxy_url_for(8000, "/ws", scheme="wss://")
            >>> import websockets
            >>> async with websockets.connect(url, additional_headers=sandbox.proxy_headers) as ws:
            ...     await ws.send("hello")
            ```
        z://r   /z/proxy/)rF  r   rv  
startswithrH  )rG   r  rl   r  host_and_rests        r3   proxy_url_forzSandbox.proxy_url_forT  s^    L --33E1=bAs+tt-(9tfMMr5   c                 b    d| j                   j                   | j                   j                  dS )zPAuth headers to send with [`proxy_url_for`] requests (HF token + sandbox token).r   r   )rF  r   r   rF   s    r3   proxy_headerszSandbox.proxy_headers~  s0      't||'?'?&@A#||::
 	
r5   c                 <    d| j                   d| j                  dS )NzSandbox(id=z, image=rL   )r  r   rF   s    r3   rO   zSandbox.__repr__  s    TWWKx

~Q??r5   r
  resourcec                 X     | j                   j                  || j                  |z   fi |S rz   )rF  r  rH  )rG   r
  r  r  s       r3   rf   zSandbox._request  s)    #t||##FDOOh,FQ&QQr5   c              +      K    | j                   j                  || j                  |z   fi |5 }| d d d        y # 1 sw Y   y xY wwrz   )rF  r  rH  )rG   r
  r  r  r   s        r3   r   zSandbox._stream  sF      T\\  8)CNvN 	RZN	 	 	s   ,A<	AAArh   )r+   r_   )r  ),rP   rQ   rR   rS   rU   r   rV   r}   r/  DEFAULT_IMAGEDEFAULT_IDLE_TIMEOUTr9   r:   dictr	   r   r   rn  r  r2  rg   r>  r  r  r  r   r   r   r=   r  rZ   r  rW   r   r  r  r  rO   r   r0  rf   r   r   r   rX   r5   r3   r_   r_     s   
( ( 	(
 *( ( ( 
(:  #ba "1E%))-'+ $!&$ baba 	ba
 EkC'$.ba #s(^d"ba c3h$&ba f$ba :ba ba ba Tzba 
ba baH AE[_ $b $bC$J $bcTXj $bdm $b $bP =D!.! 
 !%( #2525%(#49_# d{	#
 #s(^d"# 4Z# # Tz# SE4K(4/# SE4K(4/# # EN# 
# # 
 !%(49_ d{	
 #s(^d" 4Z DM 
  "%) $ 2626 [49_[ d{	[
 #s(^d"[ 4Z[ [ Tz[ SE4K(4/[ SE4K(4/[ [ [ 
	.[z
4/ 
. "sTz " " Kt K K(Nz (N#) (N3 (N (N^a (NT 
tCH~ 
 
@# @
Rs Rc R R c S x?W  r5   r_   c                   4   e Zd ZdZefdedddeddddd
ded	ed
edededz  dedz  dee	z  ez  dz  dedz  de	dedz  de
ddfdZeddddededz  dedz  dd fd       Zd9dedee   fdZdedddeeef   dz  dee	z  ez  dz  de
ddfdZedefd       Zedefd        Zedee   fd!       Zd:d"Zd;d#Zd:d$Zd:d%Zd<d'Zd(ed)   dd&fd*Zde
fd+Zd:d,Zd-edee   fd.Z defd/Z!d0d)deeef   d1edz  dd2fd3Z"d4e#ddfd5Z$d0eddfd6Z%d:d7Z&d:d8Z'y)=SandboxPoola  A fleet of shared "host" jobs, each packing many landlock-isolated sandboxes.

    One host is one billed HF Job (a VM); it runs the sandbox server and multiplexes
    up to `sandboxes_per_host` lightweight sandboxes, isolated from each other by
    uid + the Landlock LSM. This makes large fan-outs cheap (the VM cost is shared
    across all its sandboxes) and fast (creating a sandbox is ~one proxy round-trip
    once a host is warm). Best for many parallel CPU sandboxes such as RL rollouts;
    for GPU or strong VM-level isolation between mutually-distrusting workloads, use
    [`Sandbox.create`] instead.

    The constructor pre-provisions `warm_up` hosts (default 1) and blocks until they are
    ready; further hosts are then provisioned on demand as sandboxes are requested, and all
    are torn down on `close()` (or when idle, via `idle_timeout`). The user never manages jobs:

    ```python
    >>> from huggingface_hub import SandboxPool
    >>> with SandboxPool(image="python:3.12", flavor="cpu-basic", warm_up=2) as pool:
    ...     boxes = [pool.create() for _ in range(100)]   # packed across the warm hosts
    ...     print(boxes[0].run("echo hi").stdout)
    hi
    ```

    `create()` makes **one** sandbox at a time: it reuses a host that still has free
    capacity before booting a new one, so you grow on demand as work arrives. To avoid
    a cold start on the first few calls, pre-provision hosts with `warm_up` (or
    [`warm`]). Warm hosts are discovered via job labels, so reuse works **across
    processes** too (a fresh pool with the same `image`/`flavor`/`name` attaches to
    hosts an earlier run left behind):

    ```python
    >>> pool = SandboxPool(image="python:3.12")
    >>> sbx = pool.create()    # finds a warm host (here or in another process), else boots one
    ```
    rN  r   NrO  F)
rP  sandboxes_per_hostwarm_up	max_hostsrk   rQ  r  r  r;  _connect_moder   rP  r  r  r  rk   rQ  r  r  r;  r  r+   c       
         &   |dk  rt        d      |dk  rt        d      t        |
      | _        || _        || _        || _        || _        || _        ||ndt        d       | _	        || _
        || _        |	| _        g | _        t        j                         | _        t        j                         | _        t        j                         | _        d| _        d| _        || _        | | _        t/               | _        |s| j3                          yy)	u	  Configure a pool and pre-provision `warm_up` hosts (blocks until they are ready).

        Env/secrets are *not* set here: they belong to each sandbox and are passed to
        `create(env=...)`, so sandboxes in the same pool can have different environments.

        Args:
            image (`str`, *optional*, defaults to `"python:3.12"`):
                Docker image for the hosts (needs `/bin/sh`). All sandboxes in the
                pool share this image.
            flavor (`str`, *optional*, defaults to `"cpu-basic"`):
                Hardware flavor for the host jobs (e.g. `"cpu-basic"`).
            sandboxes_per_host (`int`, *optional*, defaults to `50`):
                How many sandboxes to pack per host (per VM density).
            warm_up (`int`, *optional*, defaults to `1`):
                How many hosts to pre-provision in the constructor (which blocks
                until they are ready), so an initial burst of `create()` calls doesn't pay
                a host cold start each. Existing warm hosts (from the cache / other processes)
                count towards it, so only the shortfall is booted; capped by `max_hosts`.
                Defaults to 1 (a single host).
            max_hosts (`int`, *optional*):
                Optional cap on the number of host jobs (a cost ceiling). When
                reached and all hosts are full, `create()` raises.
            name (`str`, *optional*):
                Pool name, used as the `hf-sandbox-pool` job label so the pool is
                discoverable (e.g. `hf sandbox pool ls`, `connect()`). `create()` reuses
                running hosts carrying this label (including from other processes) before
                booting new ones, so distinct names keep separate pools from sharing hosts.
                A random name is generated when omitted.
            idle_timeout (`int` or `float` or `str`, *optional*, defaults to `600`):
                Host idle timeout — a host shuts down once it has had no
                sandboxes for this long (a billing backstop). Each sandbox also has its
                own idle timeout, set at `create()`. Pass `None` to disable.
            namespace (`str`, *optional*):
                User or org namespace to run hosts under.
            start_timeout (`float`, *optional*, defaults to `120.0`):
                Max seconds to wait for a host to become ready.
            token (`str`, *optional*):
                HF token override.
        r   z sandboxes_per_host must be >= 1.zwarm_up must be >= 1.rW  Nzpool-   F)r   r   r   r   rP  r  _warm_upr  r   rk   _idle_timeout
_namespace_start_timeout_hosts	threadingLock_lock_warmup_lock
_boot_lock_closed
_warmed_up_require_live_host_owns_hostsset_dead_host_ids_ensure_warmed_up)rG   r   rP  r  r  r  rk   rQ  r  r  r;  r  s               r3   r}   zSandboxPool.__init__  s   l !?@@Q;455&	
"4" ,DE)A,2H	)#+,.^^%
 &NN,
 $..* #0  -,(+
 ""$ r5   r:  pool_idc                d   t        |      }|Q | |j                  |j                  |j                  |j                  ||j
                  ||j                  n||d	      S t        |      }t        |||      }t        |||      }|j                  d      }|j                  d      }	 | |j                  xs |j                  xs t        |j                  t        |j                        ndt        |j                  d	t                     |	t        |	      nd||t        |      nd||d	      S )
ur  Reattach to a running pool by id, from any machine — no local state needed.

        Finds a running host labelled with `pool_id` and rebuilds the pool's config
        (image/flavor/density/host-idle) from that host job's spec and env vars, returning
        a [`SandboxPool`] ready to `create()` more sandboxes — packing onto the running
        hosts, or booting a duplicate (same config) when they are full.

        Raises [`SandboxError`] if no running host is found (a pool stops existing once
        all of its hosts are gone — idle-timed-out or killed).

        Args:
            pool_id (`str`):
                The id returned when the pool was first created.
            namespace (`str`, *optional*):
                Namespace to search for the pool's hosts (defaults to yours).
            token (`str`, *optional*):
                HF token override.
        NT)	r   rP  r  r  rk   rQ  r  r;  r  rW  r!  SBX_IDLE_TIMEOUTSBX_MAX_HOSTSrN  SBX_CAPACITY)r   r   rP  r  r  rQ  r  r   _find_pool_host_job	_host_envr   r  r  r  rU   r9   DEFAULT_SANDBOXES_PER_HOST)
r  r  r  r;  cacher   r   rR  idle_rawmax_hosts_raws
             r3   r  zSandboxPool.connect  s   .  (kk||#(#;#;//"//-6->%//I"
 
 % !#w)DSI677-.0""CcllCm&)jj&<3szz?+"377>;U#VW,9,Ec-(4*2*>XD

 
	
r5   	num_hostsc                    | j                   rt        d      | j                          | j                  5  |t	        | j
                        z
  }ddd       dkD  rA| j                  |      }| j                  5  | j
                  j                  |       ddd       | j                  5  d| _	        ddd       | j                          | j                  S # 1 sw Y   xY w# 1 sw Y   MxY w# 1 sw Y   =xY w)u  Ensure `num_hosts` empty host(s) are running and leave them running. Returns the
        pool's host job ids.

        Used to "create" a pool up front: the hosts carry the pool label and config (in
        their env vars), so a later `SandboxPool.connect(pool_id)` (even from another
        machine) finds them and spawns sandboxes without a cold start. The hosts keep
        billing until killed or idle.

        Adopts hosts already running for this pool (found via job labels) before booting,
        so a `warm()` after `connect()` — or a repeated `warm()` — tops up to `num_hosts`
        instead of duplicating live hosts and blowing past `max_hosts`.
        This SandboxPool is closed.Nr   T)r  r   _discover_hostsr  rM   r  _provision_hostsextendr  r  _save_cachehost_ids)rG   r  	shortfallbooteds       r3   warmzSandboxPool.warmS  s     <<<==ZZ 	5!C$44I	5q=**95F +""6*+ 	#"DO	#}}	5 	5+ +	# 	#s#   C7C'C+CC(+C4)rR  rQ  rU  rR  rU  r_   c                   | j                   rt        d      t        |xs i       }|rt        | j                        |d<   |t        |      nd}g }d}d}	 | j                          	 | j                         }	|	$|s"d}| j                          | j                         }	|	Y| j                  r<|r:| j                  s.t        | j                         t        d| j                   d      | j                  |      }	|	=	 | j                  |	||      }
|
| j'                          |
S |	|v r|j)                  |	       d}|d	z  }|t*        kD  rt        d
      # t        $ rA | j                   5  t#        d|	j$                  d	z
        |	_        ddd        # 1 sw Y    xY ww xY w# t        $ r | j                   5  | j                  D cg c]	  }||vs| nc c}w c}| _
        ddd       n# 1 sw Y   nxY w|D ]H  }		 |	j-                          n# t        $ r Y nw xY w|	j/                          5# |	j/                          w xY w  w xY w)u  Create one sandbox, provisioning a host if needed.

        Reuses a host with free capacity (this pool's, or a warm host found via job labels
        / the local cache) before booting a new one, so a `create()` against a warm host
        costs ~one round-trip. Call it repeatedly to fan out; use `warm_up` (or [`warm`])
        to pre-provision hosts and avoid a cold start on the first calls. If a host fills
        up under us (another process packed it) or a cached host is gone, the sandbox is
        re-placed on another host (or a fresh one).

        Args:
            env (`dict[str, Any]`, *optional*):
                Environment variables for this sandbox (each sandbox gets its own).
            idle_timeout (`int` or `float` or `str`, *optional*, defaults to `600`):
                Per-sandbox idle timeout — a sandbox is evicted from its host
                after this much inactivity (no API calls, no running process). Distinct
                from the host idle timeout. Pass `None` to disable.
            forward_hf_token (`bool`, *optional*, defaults to `False`):
                If True, inject your HF token as `HF_TOKEN` in the sandbox
                (opt-in). Unlike a dedicated sandbox's `secrets`, a pooled sandbox's env is
                delivered to the host server at creation (never stored in the host job), so
                it doesn't appear in any job's metadata.
        r  HF_TOKENNFr   T No running host found for pool 'X'. The pool has stopped (all its hosts were killed or idle-timed-out); create a new one.r   zZCould not place sandbox: hosts kept reporting full. Raise max_hosts or sandboxes_per_host.)r  r   r  r   r   r;   r  _reserve_oner  r  r  r   rk   _boot_one_host_create_oner   r  maxr   r  remove_MAX_PACK_ROUNDSr  r  )rG   rR  rQ  rU  sandbox_env	idle_secs	new_hosts
discoveredroundshostr|   hs               r3   rn  zSandboxPool.createn  sc   : <<<==39"o&6tyy&AK
# 8D7O%l3UY	*,	
<	 ""$((*<
!%J((*,,.D<..:dkk *$))4*>tyyk J_ _   ..y9D #"&"2"24i"P *((*&
 y(!((."
!,,&t U 0 %  "ZZ >(+Atyy1}(=DI>>*  	 M*.++LQ)9KqLLM M M! !!OO%   JJLDJJL! 	s   B$F! <E F! #1F! F)F	FF	FF! !I6G*	GGG*!	I*G3	/I<HH-	HH-HH-I-H??Ic                 p    | j                   5  t        | j                        cddd       S # 1 sw Y   yxY w)z*Number of host jobs currently provisioned.N)r  rM   r  rF   s    r3   r  zSandboxPool.num_hosts  s,     ZZ 	$t{{#	$ 	$ 	$s   ,5c                 ~    | j                   5  t        d | j                  D              cddd       S # 1 sw Y   yxY w)z<Number of sandboxes currently handed out (across all hosts).c              3   4   K   | ]  }|j                     y wrz   )r   ).0r  s     r3   	<genexpr>z,SandboxPool.num_sandboxes.<locals>.<genexpr>  s     9Ttyy9s   N)r  sumr  rF   s    r3   num_sandboxeszSandboxPool.num_sandboxes  s2     ZZ 	:9T[[99	: 	: 	:s   3<c                     | j                   5  | j                  D cg c]  }|j                   c}cddd       S c c}w # 1 sw Y   yxY w)z!Job ids of the provisioned hosts.N)r  r  r   rG   r  s     r3   r  zSandboxPool.host_ids  s>     ZZ 	9,0KK8DDKK8	9 	98	9 	9s   A ;A A  A	c                    | j                   5  | j                  }g | _        d| _        ddd       D ]/  }	 | j                  r|j	                          |j                          1 | j                  rt        | j                         yy# 1 sw Y   `xY w# t
        $ r/}t        j                  d|j                   d|        Y d}~td}~ww xY w# |j                          w xY w)u  Release the pool. Idempotent.

        For a pool we created, this terminates all host jobs (and therefore all their
        sandboxes). For a `connect()`'d handle it only releases the local HTTP clients: the
        shared hosts may be serving other clients, so — like [`Sandbox.connect`] — leaving a
        `with` block must not tear them down. Terminate a connected pool's hosts explicitly
        with `hf sandbox pool delete <id>`.
        TNFailed to cancel sandbox host r}  )r  r  r  r  r  r   ri  rj  r   r  r   rk   )rG   hostsr  r   s       r3   r  zSandboxPool.close  s     ZZ 	 KKEDKDL	   	D##OO% 

	 dii( 	  	   T!?}BqcRSST 

s4   BBB	C%CCCCC c                     | S rz   rX   rF   s    r3   r  zSandboxPool.__enter__  r  r5   c                 $    | j                          y rz   )r  r  s     r3   r  zSandboxPool.__exit__  s    

r5   c                    | j                   5  | j                  r
	 ddd       y| j                          | j                  | j                  nt        | j                  | j                        }| j                  5  |t        | j                        z
  }ddd       dkD  r| j                          | j                  ss| j                  5  |t        | j                        z
  }ddd       |dkD  rA| j                  |      }| j                  5  | j                  j                  |       ddd       d| _        ddd       | j                  r| j                          yy# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   NxY w# 1 sw Y   KxY w)u  One-time: seed from the cache and pre-provision up to `warm_up` hosts.

        Cheap by default: seeding is local (no HTTP), and if the cache already gives at
        least `warm_up` hosts we trust them and skip discovery/booting (dead ones are
        pruned lazily by `create()`). Only when short do we list_jobs and boot the
        shortfall in parallel, capped by `max_hosts`.

        Runs at most once per pool, under `_warmup_lock`: concurrent first `create()` calls
        block here until the warm-up completes — and see its hosts — rather than racing past
        a half-set flag and each booting their own. `_warmed_up` is only set once the work
        succeeds, so a failed warm-up is retried (seeding/discovery dedupe by job id).

        Warm-up hosts are pool-level (like [`warm`]): they are not torn down if the
        triggering `create()` later fails — `close()` (or the `with` block) reclaims them.
        Nr   T)r  r  _seed_hosts_from_cacher  r  r   r  rM   r  r  r  r  r  r  )rG   targetr  r  s       r3   r  zSandboxPool._ensure_warmed_up  sL      	#	# 	# '')&*nn&<T]]#dmmUYUcUcBdF 6"S%55	61}$$& .. >$*S-=$=	> 1}!%!6!6y!A!ZZ 7 KK..v67"DO'	#, ;; #6 6> >7 7#	# 	#sY   E>AE>8E5E>E&*E>	E2%E>E#	E>&E/	+E>2E;	7E>>F_SandboxServer | Nonec                     | j                   5  | j                  D ]@  }|j                  |j                  z
  dkD  s |xj                  dz  c_        |c cddd       S  	 ddd       y# 1 sw Y   yxY w)zNReserve one slot on the first host with free capacity (under lock), else None.r   r   N)r  r  r   r   r  s     r3   r  zSandboxPool._reserve_one0  sg    ZZ 	   ==499,q0IINIK		  	  	 
 	 
 s   ,A'A'A''A0r  r   c                    | j                   5  | j                         }||cddd       S | j                         r| j                         }||cddd       S | j                  d      }|j	                  |       | j
                  5  | j                  j	                  |       ddd       | j                         cddd       S # 1 sw Y   "xY w# 1 sw Y   yxY w)uh  Provision (or adopt) one host and reserve a slot on it. Returns None to retry.

        Held under `_boot_lock` so that, within a process, only one host is booted at a time:
        a burst of create() calls that all found every host full queue here, and each new host
        frees `sandboxes_per_host` slots for the threads still waiting — so they reuse it instead
        of each booting their own. Before booting, we reuse a slot freed by a concurrent boot and,
        failing that, adopt a host already SCHEDULING for this pool (here or in another process)
        rather than piling on a duplicate.
        Nr   )r  r  _adopt_pending_hostr  r  r  r  )rG   r  r  r  s       r3   r  zSandboxPool._boot_one_host9  s     __ 	' $$&D	' 	' '')((*#	' 	' **1-FV$ +""6*+$$&!	' 	'+ +	' 	's.   C#C.CC"CC	CCc           
      0   | j                   D ch c]  }|j                   c}t        fd| j                  j	                  dt
        t        t        | j                  i| j                        D        d      yt        j                  d| j                   dj                   d       t        j                         | j                  z   }t        j                         |k  r| j                  j                  j                  | j                  	      j                   j"                  }|d
vry|dk(  r/| j%                          t'        fd| j                   D              ryt        j(                  d       t        j                         |k  ryc c}w )a  Wait for and adopt a host already SCHEDULING for this pool, if any.

        Avoids over-provisioning when a host is already on its way up for this pool (started by
        another process, or an earlier create() in this one): rather than booting a duplicate,
        wait for it to reach RUNNING and adopt it via discovery. Returns True if a pending host
        was found (whether or not it eventually came up), False if none is scheduling.
        c              3   >   K   | ]  }|j                   vr|  y wrz   )r  )r  r   knowns     r3   r  z2SandboxPool._adopt_pending_host.<locals>.<genexpr>_  s'      
 66& s   
SCHEDULINGr&  r[  r  NFzPool 'z' host z: is already SCHEDULING; waiting for it instead of booting.r  )r  rr  rr  c              3   P   K   | ]  }|j                   j                  k(    y wrz   r   r  )r  r  pendings     r3   r  z2SandboxPool._adopt_pending_host.<locals>.<genexpr>v  s     ITt{{gjj0I   #&Tg      ?)r  r   nextr   	list_jobsre  rx  
POOL_LABELrk   r  ri  debugr  r#  r  r%  r&  r'  r  anyr*  )rG   r  r+  r'  r  r
  s       @@r3   r  zSandboxPool._adopt_pending_hostU  sB    *.5599..'&	:tyyI"oo /  
 ?vdii[

|;uvw99;!4!44iikH$II))t)W^^ddE55	! $$&IT[[IIJJsO iikH$ 9 6s   Fc                    | j                   D ch c]  }|j                   }}| j                  j                  dt        t
        t        | j                  i| j                        D cg c]  }|j                  |vr| }}|D ]  d}	 t        | j                  j                  | j                        }t        | j                  | j                        }t        |j                  d| j                              |_        t!        |j#                  dd      j%                               |_        | j6                  5  t9        fd
| j                   D              r|j5                          n| j                   j;                  |       ddd        yc c}w c c}w # t(        t*        j,                  t.        f$ rB}t0        j3                  dj                   d	|        ||j5                          Y d}~ud}~ww xY w# 1 sw Y   xY w)aE  Attach to running host jobs that match this pool (image/flavor/name).

        Lets `create()` reuse a host warmed by an earlier call or another process
        instead of booting a new one. Hosts are found via job labels; each adopted
        host's free capacity is read from the server, so packing stays accurate.
        rr  r  Nr!  r  r   rp  zSkipping host z during discovery: c              3   P   K   | ]  }|j                   j                  k(    y wrz   r	  )r  r  r   s     r3   r  z.SandboxPool._discover_hosts.<locals>.<genexpr>  s     Et{{cff,Er  )r  r   r   r  re  rx  r  rk   r  r  rw  r  r9   r   r  r   rM   r  r   r   r   r   	HTTPErrorr   ri  r  r  r  r  r  )rG   r  r  r   matchesr@  rR  r   s      `    r3   r  zSandboxPool._discover_hosts{  s    *.555 yy** "Iz499E// + 

 vvU" 
 
  	/CF&tyy#&&DOOT  		3$//J"%cggnd>U>U&V"W!&.."H"M"M"OP  /EEELLNKK&&v.	/ /!	/ 6
& !%//>B  ~cffX5HLM%LLN/ /s1   F)FB&F=AHG><7G99G>H	num_newc                    | j                   5  t        | j                        }ddd       | j                  J|z   | j                  kD  r8| j                  |z
  }t	        d| d| j                   dt        d|       d      t        t        |d            5 }t        |      D cg c]  }|j                  | j                         }}ddd       g }d}D ]"  }		 |j                  |	j                                $ |*|D ]#  }	 |j                          |j                          % ||S # 1 sw Y   	xY wc c}w # 1 sw Y   txY w# t        $ r}
|
}Y d}
~
d}
~
ww xY w# t        $ r Y Ww xY w# |j                          w xY w)	z=Boot `num_new` host jobs in parallel, respecting `max_hosts`.NzPool needs z more host(s) but max_hosts=z allows only r   z. more. Raise max_hosts or kill some sandboxes.    )max_workers)r  rM   r  r  r   r  r   r   r   submit
_boot_hostr  r  r   r  r  )rG   r  currentallowedr   _futuresr  r   futurer   r@  s               r3   r  zSandboxPool._provision_hosts  s   ZZ 	'$++&G	'>>%'G*;dnn*Lnnw.GgY&B4>>BR S"1g//]_   C,<= 	QAFwPAxt7PGP	Q')"& 	Ffmmo.	
   ##%%' LLN# K;	' 	' Q	Q 	Q   !  LLNse   D*D<%"D7D<EE *D47D<<E	EEE 	E,)E/+E,,E//Fc                     t        | j                        }t        d      }t        ||      }t	        | j                  |ddd| j
                  d|      \  }}}}d|d<   t        | j                        |d<   | j                  t        | j                        |d<   t        dt        t        t        | j                  t        |i}| j                  j                  | j                   |||| j"                  t$        ||xs dt&        g| j(                  	
      }	d}
	 t*        j-                  |	||| j                  t/        | j                  d
z   d      | j                        }
|
j1                  | j2                         |
S # t4        $ r 	 | j                  j7                  |	j8                  |	j:                  j                         n;# t4        $ r/}t<        j?                  d|	j8                   d|        Y d}~nd}~ww xY w|
|
jA                           w xY w)z6Start one host job and wait until its server is ready.rx   NFrX  r   SBX_HOST_MODEr  r  rY  r(      )r   r*   r   r   r   r   r  r  r_  )!r   r   r   r4   ra  r  rU   r  r  rd  re  rx  r  rk   rg  rb  r   rP  rc  rh  r  r   r  r   r.  r  r   r  r  r   ri  rj  r  )rG   r)   r*   r   rZ  rk  rl  rm  r[  r   r@  r   s               r3   r  zSandboxPool._boot_host  s   #DII."-h>5HII++"'	6
2+{ $' 
 #&d&=&="> >>%'*4>>':GO$j)ZT_afgii**;;('4'(oo   
 +/	 $,,+II #D$;$;a$? E00 - F d112   	e		$$CFFciinn$M e!?xG_`a_bcdde!	s7   A!E8 8	H;F>=H>	G6%G1,H1G66Hr  r  r8  c                    ddi}|||d<   |r||d<   	 |j                  dd|      j                         }d|_        |j                  d      xs g }t        |j                  dd            s|s'| j                  5  |j                  |_        ddd       y|d   }t        |j                   t          |d    ||d   dd      }	| j"                  |	_        |	S # t        t        j                  f$ rM}|j
                  r t        j                  d	|j                   d
|        | j                  |       Y d}~yd}~ww xY w# 1 sw Y   yxY w)u  Create one sandbox on a reserved `host`. Returns the sandbox, or None to retry.

        None means either the host filled up between our reservation and the create
        (server-authoritative capacity — another client packed it, so we mark it full and
        place the sandbox elsewhere) or a host rebuilt from the cache is gone/unreachable
        (dropped and re-placed via discovery / a fresh boot).
        countr   Nidle_timeout_secsrR  r   rp  r  z!Dropping unreachable cached host r}  T	sandboxesrejectedr   r  Fr`  )r  r   r   r   r  r   ri  r  r   
_drop_hostr   r9   r  r   r   r_   ru  _on_sandbox_killedrK  )
rG   r  rR  r  bodyr   r   r&  rz  r|   s
             r3   r  zSandboxPool._create_one  sG    !(| (1D$%DK	<<d<CHHJD HH[)/R	txx
A&'y * MM	*|++}od4j\:$Z
  22+ eoo. 	}}LL<T[[MA3OPOOD!	*s$   "C ;ED>1AD99D>E
r|   c                     | j                   5  t        d|j                  j                  dz
        |j                  _        ddd       y# 1 sw Y   yxY w)z:Free the packing slot of a shared sandbox that was killed.r   r   N)r  r  rF  r   r{   s     r3   r)  zSandboxPool._on_sandbox_killed*  sD    ZZ 	D#&q'//*>*>*B#CGOO 	D 	D 	Ds   3A		Ac                    | j                   5  | j                  D cg c]	  }||us| c}| _        | j                  j                  |j                         ddd       |j                          yc c}w # 1 sw Y   xY w)zFForget a host found dead this session (and mark it for cache pruning).N)r  r  r  addr   r  )rG   r  r  s      r3   r(  zSandboxPool._drop_host/  sc    ZZ 	1&*kkCQd]1CDK##DKK0	1 	

 D	1 	1s    A5	A0A0-A50A55A>c                    t        | j                        }|yt        | j                        }| j                  5  | j
                  D ch c]  }|j                   }}|j                  D ]  }|j                  |v s|j                  |j                  k\  r+t        |j                  |j                  | j                  |j                  |j                  t        ||j                        | j                  t!        | j"                  dz   d      |j                  	      }|j                  |_        d|_        | j
                  j'                  |        	 ddd       yc c}w # 1 sw Y   yxY w)u  Adopt the pool's cached hosts without any HTTP (rebuilt from cached URL + nonce).

        Best-effort and unverified: each adopted host is confirmed on the first successful
        request (and dropped on the first failure, see `_create_one`). Hosts the cache
        believes are full are skipped — label discovery re-checks them with fresh counts if
        the seeded ones don't satisfy the request, so a stale-full entry never blocks a create.
        Nr(   r"  r  F)r   rk   r   r   r  r  r   r  r   r   r   r   r   r   r*   r4   r   r  r   r  )rG   r  r)   r  r  chr@  s          r3   r  z"SandboxPool._seed_hosts_from_cache8  s     		*=#DII.ZZ 	+-1[[9TT[[9E9kk +99%BKK)?'99((**[[(("7"(("K		$'(?(?!(CS$I[[
 !gg"'""6*!+	+ 	+9	+ 	+s   E	EC3EEE'c                 J   | j                   5  | j                  D cg c]b  }t        |j                  |j                  |j
                  |j                  |j                  |j                  t        j                               d }}t        | j                        }ddd       t        | j                  | j                  | j                  | j                   | j"                  | j$                  t'        | j$                        nd| j(                  	       yc c}w # 1 sw Y   xY w)zOPersist the pool config + current hosts (with their live counts) for next time.)r   r   r   r*   r   r   
updated_atN)r   rP  r  r  rQ  r  r  dead_host_ids)r  r  r   r   r   r   r*   r   r   r#  r  r  r   rk   r   rP  r  r  r  r;   r  )rG   r  r  deads       r3   r  zSandboxPool._save_cacheX  s    ZZ 	, !KK  ;;**!]]**!]]#yy{E  t**+D	, 	II**;;#66nnBFBTBTB`*4+=+=>fjoo
	
	, 	,s   DA'DDDD")r   rh   )r+   r  )r+   r  )(rP   rQ   rR   rS   r  r  r  rU   r9   r:   rV   r}   r/  r  r   r  r  r	   rn  rW   r  r  r  r  r  r  r  r  r  r  r  r   r  r  r  r_   r)  r(  r  r  rX   r5   r3   r  r    s   !J #`% ""< $1E $$ #`%`% 	`%
  `% `% :`% Dj`% EkC'$.`% :`% `% Tz`% `% 
`%H >BX\ 4
c 4
t 4
3QU: 4
an 4
 4
lc $s) < &*1E!&e #s(^d"e EkC'$.	e
 e 
eN $3 $ $
 :s : :
 9$s) 9 9
)0
'R'-=(> 'CZ '8$T $L&/P ^0D B>N >@$ 0 $tCH~ $RUX\R\ $aq $LD' Dd D
~ $ +@
r5   r  r   c                     t        | j                  t              r| j                  n	t               }|st	        d      |S )NzMA Hugging Face token is required to use sandboxes. Run `hf auth login` first.)r8   r;  rU   r   r   )r   r;  s     r3   r   r   u  s0    #CIIs3CIIEjkkLr5   rR  rS  rT  rQ  rU  r   c                   i |xs i dt        t              i}i |xs i d|i}	t        |xs g       }
|t        t        |            |d<   |r||	d<   | j                   dt
        j                   d|d<   ||	d<   t        |d	<   |
j                  t        d
t
        j                  t        d             ddt        g}|||	|
fS )aU  Build the (command, env, secrets, volumes) to launch a job running sbx-server.

    Shared by dedicated sandboxes and shared hosts: both fetch and exec the same unified
    `sbx-server` binary at startup (via `/bin/sh`), downloading it with wget/curl, or
    reading it off the always-mounted server bucket when the image ships neither.
    SBX_PORT	SBX_TOKENr  r  z	/buckets/z/resolve/sbx-serverSBX_SERVER_URLSBX_DL_TOKENSBX_SERVER_MOUNTbucketT)rp   source
mount_path	read_onlyz/bin/shz-c)rU   rh  r   r;   endpointr   SANDBOX_SERVER_BUCKET_SERVER_MOUNT_PATHr  r   _BOOTSTRAP_DOWNLOAD)r   r)   rR  rS  rT  rQ  rU  r   rk  rl  rm  rZ  s               r3   ra  ra  |  s    & T#)Sj#>Q:RSG"Qgm"Qk="QKw}"%K&)*;L*I&J"#"*J#&<<.	):Y:Y9ZZm nG"*K #5GHY%D%DQcost $ 34GG[+55r5   r   r  c                    t        |j                  t              r|j                  ni }d|vr?| j                  |j                  |      j                  xs i }t        |t              r|ni }|S )z6Return a host job's env vars (where pool config lives)r  r  )r8   environmentr  r%  r  )r   r   r  rR  s       r3   r  r    sY    '>#//BCS ooSVVyoAMMSQST*cJr5   r!  r  c                x    | j                  dt        t        t        |i|      D ]  }|c S  t	        d| d      )zNReturn any running host job belonging to `pool_id` (found via the pool label).rr  r  r  r  )r  re  rx  r  r   )r   r  r  r   s       r3   r  r    sY    }}*iW!MYb    
 
*7) 4K 	K r5   ry  c          	         | j                  ||      }|j                  xs i }|j                  t              }||j                  t              t
        k7  rt        d| d      |j                  j                  dk7  r&t        d| d|j                  j                   d      t        j                  ||t        t        |       |      | t        j                  dz   	      S )
z?Reattach to a running host job and return its server transport.r  rq  z is not a sandbox host.rr  zSandbox host rs  r"  r]  r^  )r%  r[  r   rg  re  rx  r   r&  r'  r   r  r4   r   rv   r   )r   ry  r  r   r[  r*   s         r3   rw  rw    s    
//	/
BCZZ2FJJ{#E}

:.);T+.EFGG
zz9$];-7PQTQ[Q[QaQaPbbdeff""+,<S,A5I$99A= #  r5   r<  c                     d| vr| |fS | j                  dd      \  }}|r|rd|v rt        d| d      |||k7  rt        d|d|d      ||fS )zPAccept `namespace/sandbox_id` ids (as shown in the Hub UI), like `hf jobs` does.r  r   z?Sandbox id must be 'sandbox_id' or 'namespace/sandbox_id', got r#   z%Conflicting namespace: got namespace=z" but sandbox id implies namespace=)rv  r   )r<  r  extracted_namespace	parsed_ids       r3   rt  rt    s    
*9$$%/%5%5c1%="i3)3C\]g\jjklmm.A!A3I=@bcvbyyz{
 	
 )))r5   c                     | j                   j                  xs g D ]  }dt         d|v s|c S  t        d| j                   dt         d      )Nz--r#   rq  z) does not expose the sandbox server port )r&  expose_urlsrh  r   r  )r   urls     r3   r  r    s\    zz%%+ #$A&#-J cffX%NObNccde
ffr5      )r  limitr   rN  c                    	 t        | j                  ||            | d  }|rddj                  d |D              z   S dS # t        $ r Y yw xY w)Nr  rs   z Last logs:

c              3   &   K   | ]	  }d |   yw)z  NrX   )r  r   s     r3   r  z!_tail_job_logs.<locals>.<genexpr>  s     &EtD6{&Es   )r   fetch_job_logsr   r   )r   r   r  rN  liness        r3   r)  r)    sd    S''v'KLeVWU JO?TYY&Eu&EEEVTVV  s    A 	AA)^r0   r-   r   r  r#  concurrent.futuresr   
contextlibr   dataclassesr   r   pathlibr   rS  r   typingr	   r
   r   r   r   r   r   r   rs   r   _sandbox_cacher   r   r   r   
_space_apir   errorsr   r   r   hf_apir   r   utilsr   r   utils._parsingr   
get_loggerrP   ri  rh  rd  re  rf  rx  r  rg  r  r  rc  r  ru  r(  r  rA  rB  rU   r4   r9   r:   r;   r=   rZ   rj   rv   rV   r  r   r0  r   r   r   r2  r_   r  r   r   r   ra  r  r  rw  rt  r  r)  rX   r5   r3   <module>r`     s        1 % (   M M M     E E " % * 
		H	%   
	 
 !    A    ' 	 cC c c c$ec 1 $c $ s s s& C C C.   AN ANH	sT#Y 	td{ 	tCH~ 	5>> htn s s4 sxk xkv "{ {|^
 ^
B% C $6	$6$6 
c3h$		$6
 #s(^d"$6 &\D $6 +#d*$6 $6 $6 49d38nd38nd6lBC$6N5 w cDj T#s(^  NR 	U 	S 	d
 	V] 	 LP u 3 cDj Tb $*# *#* *sCRVJAW *g' gc g HLZ\ W Ws W#* WTW Wad Wr5   