o
    ;iw                     @   s  d dl Z d dlZd dlZd dlZd dlZd dlZd dlmZmZ d dl	m
Z
 d dlmZmZmZmZ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 dd	lmZmZ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) ddl*m+Z+ er	 de,ddde,de#dee-ee. e,f df f
ddZ/	d:de,ddde#de0dee1 dee-ee. e0f df fddZ2ede,e.Z3G dd dee3 Z4dee.df dee,df fd d!Z5dee.df dee.df fd"d#Z6e
G d$d% d%Z7d&e7dee.df fd'd(Z8G d)d* d*Z9G d+d, d,Z:G d-d. d.ee3 Z;G d/d0 d0ee3 Z<G d1d2 d2ee3 Z=d3Z>G d4d5 d5Z?G d6d7 d7Z@G d8d9 d9ZAee=ZBeeAZCdS );    N)AsyncGeneratorAsyncIterator)	dataclass)TYPE_CHECKINGGenericLiteralOptionalTextIOTypeVarUnioncast)StreamTerminatedError)ClientClosedExecTimeoutErrorInvalidError)api_pb2   )aclosingsynchronize_apisynchronizer)TaskCommandRouterClient)_Client)logger)ConflictErrorInternalErrorServiceError)
StreamType
sandbox_idfile_descriptor api_pb2.FileDescriptor.ValueTypelast_entry_idclientreturnc                 C  sp   t j| |d|d}|jj|2 z#3 d H W }|j}|jD ]}|jd|fV  q|j	r4d |fV   d S q6 d S )N7   )r   r   timeoutr    utf-8)
r   SandboxGetLogsRequeststubSandboxGetLogsunary_streamentry_iditemsdataencodeeof)r   r   r    r!   req	log_batchmessage r2   D/home/ubuntu/.local/lib/python3.10/site-packages/modal/io_streams.py_sandbox_logs_iterator%   s    

r4   
process_id
last_indexdeadlinec           
      C  s   t j| d|d|d}|jj|}	 z|r|t  nd }tj|	 |dI d H }W n tj
y9   dV  Y d S  tyB   Y d S w |jD ]	}	|	j|jfV  qF|dr]d |jfV  d S q)Nr#   T)exec_idr$   r   get_raw_byteslast_batch_index)r$   )N	exit_code)r   ContainerExecGetOutputRequestr'   ContainerExecGetOutputr)   time	monotonicasynciowait_for	__anext__TimeoutErrorStopAsyncIterationr+   message_bytesbatch_indexHasField)
r5   r   r!   r6   r7   r/   stream	remainingbatchitemr2   r2   r3    _container_process_logs_iterator8   s2   

rM   Tc                   @   s  e Zd ZU dZeeedf  ed< ej	dddfddde
d	ed
 dededededee ddfddZedefddZdefddZdd Zdeeee e
f df fddZd$dedeedf fddZdeedf fddZdeedf fd d!Zd"d# ZdS )%_StreamReaderThroughServerz9A StreamReader implementation that reads from the server.N_streamTFr   r   	object_idobject_typesandboxcontainer_processr!   stream_typetextby_liner7   r"   c	           	      C   s   || _ || _|| _|| _d| _d| _d| _|| _|dkr"|s"td|| _	|| _
d| _t|ts9tdt| |dkrF|tjkrFtd|| _| jd	kr[g | _t|  | _dS dS )
mdmd:hiddenN     rT   z,Sandbox streams must have text mode enabled.Fz,stream_type must be of type StreamType, got zSandbox streams must be piped.rU   )_file_descriptor_object_type
_object_id_clientrP   _last_entry_id_line_buffer	_deadline
ValueError_text_by_liner.   
isinstancer   	TypeErrortypePIPE_stream_type_container_process_bufferrA   create_task!_consume_container_process_stream_consume_container_process_task)	selfr   rQ   rR   r!   rV   rW   rX   r7   r2   r2   r3   __init__b   s,   

z#_StreamReaderThroughServer.__init__c                 C      | j S z6Possible values are `1` for stdout and `2` for stderr.r\   ro   r2   r2   r3   r      s   z*_StreamReaderThroughServer.file_descriptorc                    s   t | j d| j d | jr<t }t|  2 z3 dH W }|	| q6 t | j d| j d t
t| S t }|  2 z3 dH W }|	| qD6 t | j d| j d t
t| S )2Fetch the entire contents of the stream until EOF.z StreamReader fd=z read startingNz read completed after EOF)r   debugr^   r\   rd   ioStringIO_decode_bytes_stream_to_str	_get_logswriter   rN   getvalueBytesIO)ro   bufferr1   r2   r2   r3   read   s   z_StreamReaderThroughServer.readc                    sn  | j tjkr	dS d}d}d}|s| jrt | jkrdS zBt| j| j| j	|| j}|2 z03 dH W \}}| j tj
krF|rFt|ddd n| j tjkrR| j| |du rZd} n|}q,6 W nQ ttttfy } zA|dkr|d	8 }t|ttfrtd
I dH  W Y d}~qt|trW Y d}~qt|trW Y d}~dS t| j d|  |d}~ww |rdS dS )zFConsume the container process stream and store messages in the buffer.NF
   r   r%   rZ   )endTr         ?z5 stream read failure while consuming process output: )rj   r   DEVNULLrb   r?   r@   rM   r^   r\   r_   STDOUTprintdecoderi   rk   appendr   r   r   r   rf   rA   sleepr   error)ro   	completedretries_remainingr6   iteratorr1   rG   excr2   r2   r3   rm      sN   

z<_StreamReaderThroughServer._consume_container_process_streamc                 C  sj   d}| j rt| j d }	 |t| jkrtdI dH  q| j| }|t|fV  |du r0dS |d7 }q)z3Streams the container process buffer to the reader.r   r   Tg?N)r`   intlenrk   rA   r   str)ro   r*   rL   r2   r2   r3   _stream_container_process   s   
z4_StreamReaderThroughServer._stream_container_processskip_empty_messagesc              
   C  s$  | j tjkrtd| jrdS d}d}|sz=| jdkr(t| j| j| j	| j
}n|  }|2 z"3 dH W \}}|| _	|r@|dkr@q.|du rMd}d| _ W dS |V  q.6 W n8 tttfy } z)|dkr|d	8 }t|ttfr{td
I dH  W Y d}~qt|trW Y d}~q d}~ww |rdS dS )a%  Streams sandbox or process logs from the server to the reader.

        Logs returned by this method may contain partial or multiple lines at a time.

        When the stream receives an EOF, it yields None. Once an EOF is received,
        subsequent invocations will not yield logs.
        z6Logs can only be retrieved using the PIPE stream type.NFr   rT   r[   Tr   r   r   )rj   r   ri   r   r.   r]   r4   r^   r\   r`   r_   r   r   r   r   rf   rA   r   )ro   r   r   r   r   r1   r*   r   r2   r2   r3   rz      sH   

z$_StreamReaderThroughServer._get_logsc                 C  s   |   2 z-3 dH W }t|tsJ |  j|7  _d| jv r2| jdd\}| _|d V  d| jv sq6 | jr@| jV  d| _dS dS )z;Process logs from the server and yield complete lines only.N   
r   r[   )rz   rf   bytesra   split)ro   r1   liner2   r2   r3   _get_logs_by_line  s   



z,_StreamReaderThroughServer._get_logs_by_linec                 C   sF   | j s | jr|  }n|  }| jrt|}tttd f || _ | j S N)	rP   re   r   rz   rd   ry   r   r   rN   )ro   rI   r2   r2   r3   	__aiter__&  s   
z$_StreamReaderThroughServer.__aiter__c                    s    | j r| j  I dH  dS dS rY   N)rP   aclosert   r2   r2   r3   r   4  s   z!_StreamReaderThroughServer.aclose)T)__name__
__module____qualname____doc__r   r   rN   __annotations__r   ri   r   r   r   boolfloatrp   propertyr   r   r   rm   tupler   r   rz   r   r   r   r2   r2   r2   r3   rO   ]   sF   
 	

0"*2rO   rI   c                 C  s^   t ddd}| 2 z3 dH W }|j|dd}|r|V  q6 |jddd}|r-|V  dS dS )	a	  Incrementally decode a bytes async generator as UTF-8 without breaking on chunk boundaries.

    This function uses a streaming UTF-8 decoder so that multi-byte characters split across
    chunks are handled correctly instead of raising ``UnicodeDecodeError``.
    r%   strict)errorsNF)finalr[   T)codecsgetincrementaldecoderr   )rI   decoderrL   rW   tailr2   r2   r3   ry   :  s   
ry   c                 C  s   d}zC| 2 z&3 dH W }t |tsJ ||7 }d|v r,|dd\}}|d V  d|v sq6 |r=|V  W |  I dH  dS W |  I dH  dS |  I dH  w )zYield complete lines only (ending with 
), buffering partial lines until complete.

    When this generator returns, the underlying generator is closed.
    r[   Nr   r   )rf   r   r   r   )rI   line_bufferr1   r   r2   r2   r3   _stream_by_lineL  s"   
"r   c                   @   s:   e Zd ZU ded< eed< eed< eed< ee ed< dS )'_StreamReaderThroughCommandRouterParamsr   r   task_idrQ   command_router_clientr7   N)r   r   r   r   r   r   r   r   r2   r2   r2   r3   r   `  s   
 r   paramsc              	   C  s   t | j| j| j| j| j4 I dH D}z|2 z3 dH W }t|jdkr)t	d|jV  q6 W n t
yL   td| j  Y W d  I dH  dS w W d  I dH  dS 1 I dH s^w   Y  dS )z(Stream raw bytes from the router client.Nr   z4Received empty message streaming stdio from sandbox.z1Deadline exceeded while streaming stdio for exec )r   r   exec_stdio_readr   rQ   r   r7   r   r,   rc   r   r   rv   )r   rI   rL   r2   r2   r3   !_stdio_stream_from_command_routeri  s(   
.r   c                   @   sj   e Zd ZdZdeddfddZedefddZde	fd	d
Z
dee	df fddZdeddfddZdS )&_BytesStreamReaderThroughCommandRouterz
    StreamReader implementation that will read directly from the worker that
    hosts the sandbox.

    This implementation is used for non-text streams.
    r   r"   Nc                 C   s   || _ d | _d S r   )_paramsrP   )ro   r   r2   r2   r3   rp     s   
z/_BytesStreamReaderThroughCommandRouter.__init__c                 C      | j jS r   r   r   rt   r2   r2   r3   r        z6_BytesStreamReaderThroughCommandRouter.file_descriptorc                    0   t  }| 2 z3 d H W }|| q6 | S r   )rw   r}   r{   r|   ro   r~   partr2   r2   r3   r        z+_BytesStreamReaderThroughCommandRouter.readc                 C   s
   t | jS r   )r   r   rt   r2   r2   r3   r        
z0_BytesStreamReaderThroughCommandRouter.__aiter__output_streamc                    s0   | 2 z3 d H W }|j | |j   q6 d S r   )r~   r{   flush)ro   r   r   r2   r2   r3   
_print_all  s
   z1_BytesStreamReaderThroughCommandRouter._print_all)r   r   r   r   r   rp   r   r   r   r   r   r   r   r	   r   r2   r2   r2   r3   r     s    
r   c                   @   sn   e Zd ZdZdededdfddZedefdd	Z	de
fd
dZdee
df fddZdeddfddZdS )%_TextStreamReaderThroughCommandRouterz
    StreamReader implementation that will read directly from the worker
    that hosts the sandbox.

    This implementation is used for text streams.
    r   rX   r"   Nc                 C   s   || _ || _d S r   )r   re   )ro   r   rX   r2   r2   r3   rp     s   
z._TextStreamReaderThroughCommandRouter.__init__c                 C   r   r   r   rt   r2   r2   r3   r     r   z5_TextStreamReaderThroughCommandRouter.file_descriptorc                    r   r   )rw   rx   r{   r|   r   r2   r2   r3   r     r   z*_TextStreamReaderThroughCommandRouter.readc              
   C  s   t t| j4 I d H E}| jrtt|}nt|}t |4 I d H  |2 z	3 d H W }|V  q&6 W d   I d H  n1 I d H sAw   Y  W d   I d H  d S 1 I d H sWw   Y  d S r   )r   r   r   re   ry   r   )ro   bytes_streamrI   r   r2   r2   r3   r     s   *.z/_TextStreamReaderThroughCommandRouter.__aiter__r   c              	      sf   t |  4 I d H }|2 z3 d H W }|| q6 W d   I d H  d S 1 I d H s,w   Y  d S r   )r   r   r{   )ro   r   rI   r   r2   r2   r3   r     s   .z0_TextStreamReaderThroughCommandRouter._print_all)r   r   r   r   r   r   rp   r   r   r   r   r   r   r   r	   r   r2   r2   r2   r3   r     s    
r   c                   @   s   e Zd ZU dZeeef ed< deeef ddfddZe	de
fdd	Zdd
dZdefddZdee fddZdefddZdd ZdS )/_StdoutPrintingStreamReaderThroughCommandRoutera  
    StreamReader implementation for StreamType.STDOUT when using the task command router.

    This mirrors the behavior from the server-backed implementation: the stream is printed to
    the local stdout immediately and is not readable via StreamReader methods.
    _readerreaderr"   Nc                 C   s   || _ d | _|   d S r   )r   _task_start_printing_task)ro   r   r2   r2   r3   rp     s   z8_StdoutPrintingStreamReaderThroughCommandRouter.__init__c                 C   r   r   )r   r   rt   r2   r2   r3   r     r   z?_StdoutPrintingStreamReaderThroughCommandRouter.file_descriptorc                    s    fdd}t |  _d S )Nc               
      sT   z j tjI d H  W d S  ty) }  ztd|   W Y d } ~ d S d } ~ ww )NzError printing stream: )r   r   sysstdout	Exceptionr   	exception)ert   r2   r3   _run  s   zR_StdoutPrintingStreamReaderThroughCommandRouter._start_printing_task.<locals>._run)rA   rl   r   )ro   r   r2   rt   r3   r     s   zD_StdoutPrintingStreamReaderThroughCommandRouter._start_printing_taskc                    
   t dNz8Output can only be retrieved using the PIPE stream type.r   rt   r2   r2   r3   r        z4_StdoutPrintingStreamReaderThroughCommandRouter.readc                 C      t dr   r   rt   r2   r2   r3   r        z9_StdoutPrintingStreamReaderThroughCommandRouter.__aiter__c                    r   r   r   rt   r2   r2   r3   rC     r   z9_StdoutPrintingStreamReaderThroughCommandRouter.__anext__c                    s\   | j d ur,| j   ttj | j I d H  W d    n1 s"w   Y  d | _ d S d S r   )r   cancel
contextlibsuppressrA   CancelledErrorrt   r2   r2   r3   r     s   


z6_StdoutPrintingStreamReaderThroughCommandRouter.acloser"   N)r   r   r   r   r   r   r   r   rp   r   r   r   r   rN   r   r   r   rC   r   r2   r2   r2   r3   r     s   
 

	
	r   c                   @   sb   e Zd ZdZdddZedefdd	Zdefd
dZ	de
e fddZdefddZdd ZdS )_DevnullStreamReaderzStreamReader implementation for a stream configured with
    StreamType.DEVNULL. Throws an error if read or any other method is
    called.
    r   r   r"   Nc                 C   s
   || _ d S r   rs   )ro   r   r2   r2   r3   rp     r   z_DevnullStreamReader.__init__c                 C   rq   r   rs   rt   r2   r2   r3   r     s   z$_DevnullStreamReader.file_descriptorc                    r   )NzEread is not supported for a stream configured with StreamType.DEVNULLrc   rt   r2   r2   r3   r     r   z_DevnullStreamReader.readc                 C   r   )NzJ__aiter__ is not supported for a stream configured with StreamType.DEVNULLr   rt   r2   r2   r3   r     r   z_DevnullStreamReader.__aiter__c                    r   )NzJ__anext__ is not supported for a stream configured with StreamType.DEVNULLr   rt   r2   r2   r3   rC     r   z_DevnullStreamReader.__anext__c                    r   )NzGaclose is not supported for a stream configured with StreamType.DEVNULLr   rt   r2   r2   r3   r     r   z_DevnullStreamReader.aclose)r   r   r"   N)r   r   r   r   rp   r   r   r   rN   r   r   r   rC   r   r2   r2   r2   r3   r     s    
r   c                   @   s   e Zd ZU dZeeeeee	f e
d< dZeeedf  e
d< ejdddddfddd	ed
ed dededededee dee dee ddfddZedefddZdefddZdeedf fddZdefddZdd ZdS ) _StreamReaderzRetrieve logs from a stream (`stdout` or `stderr`).

    As an asynchronous iterable, the object supports the `for` and `async for`
    statements. Just loop over the object to read in chunks.
    _implN	_read_genTFr   r   rQ   rR   rS   r!   rV   rW   rX   r7   r   r   r"   c              	   C   s   t  tjddksJ |r|std|	du r&t||||||||| _dS |
dus,J |dks2J |tjkr>t	|| _dS |tj
ksJ|tjksJJ t||
||	|}|rZt||}nt|}|tjkrjt|| _dS || _dS )rY   F)startz/line-buffering is only supported when text=TrueNrU   )rA   get_running_loopr   	_get_looprc   rO   r   r   r   r   ri   r   r   r   r   r   )ro   r   rQ   rR   r!   rV   rW   rX   r7   r   r   r   r   r2   r2   r3   rp   (  s*   




z_StreamReader.__init__c                 C   r   rr   )r   r   rt   r2   r2   r3   r   V  s   z_StreamReader.file_descriptorc                    s   t t| j I dH S )ru   N)r   rN   r   r   rt   r2   r2   r3   r   [  s   z_StreamReader.readc                 C   s&   | j stttd f | j | _ | j S r   )r   r   r   rN   r   r   rt   r2   r2   r3   r   _  s   z_StreamReader.__aiter__c                    s*   | j s|   | j sJ | j  I dH S )a  Deprecated: This exists for backwards compatibility and will be removed in a future version of Modal

        Only use next/anext on the return value of iter/aiter on the StreamReader object (treat streamreader as
        an iterable, not an iterator).
        N)r   r   rC   rt   r2   r2   r3   rC   d  s
   
z_StreamReader.__anext__c                    s&   | j r| j  I dH  d| _ dS dS r   )r   r   rt   r2   r2   r3   r   o  s
   
z_StreamReader.aclose)r   r   r   r   r   rO   r   r   r   r   r   r   r   r   rN   r   ri   r   r   r   r   r   r   rp   r   r   r   r   r   rC   r   r2   r2   r2   r3   r     s\   
 	

.r   i    c                   @   sn   e Zd ZdZdeded deddfdd	Zdefd
dZ	de
eeeef ddfddZdddZdddZdS )_StreamWriterThroughServerbProvides an interface to buffer and write logs to a sandbox or container process stream (`stdin`).rQ   rR   rS   r!   r"   Nc                 C   s*   d| _ || _|| _|| _d| _t | _dS )rY   r   FN)_indexr^   r]   r_   
_is_closed	bytearray_buffer)ro   rQ   rR   r!   r2   r2   r3   rp   |  s   z#_StreamWriterThroughServer.__init__c                 C   s   | j }|  j d7  _ |S )Nr   )r   )ro   indexr2   r2   r3   _get_next_index  s   z*_StreamWriterThroughServer._get_next_indexr,   c                 C   sv   | j rtdt|ttttfr1t|tr|d}t| j	t| t
kr)td| j	| dS tdt|j )zWrite data to the stream but does not send it immediately.

        This is non-blocking and queues the data to an internal buffer. Must be
        used along with the `drain()` method, which flushes the buffer.
        $Stdin is closed. Cannot write to it.r%   9Buffer size exceed limit. Call drain to flush the buffer./data argument must be a bytes-like object, not Nr   rc   rf   r   r   
memoryviewr   r-   r   r   MAX_BUFFER_SIZEBufferErrorextendrg   rh   r   ro   r,   r2   r2   r3   r{     s   

z _StreamWriterThroughServer.writec                 C   s
   d| _ dS )  Close the write end of the stream after the buffered data is drained.

        If the process was blocked on input, it will become unblocked after
        `write_eof()`. This method needs to be used along with the `drain()`
        method, which flushes the EOF to the process.
        TNr   rt   r2   r2   r3   	write_eof  s   
z$_StreamWriterThroughServer.write_eofc              
      s   t | j}| j  |  }z5| jdkr+| jjtj	| j
|| j|dI dH  W dS | jjtj| j
tj||| jddI dH  W dS  tyV } ztt|d}~ww )zFlush the write buffer and send data to the running process.

        This is a flow control method that blocks until data is sent. It returns
        when it is appropriate to continue writing data to the stream.
        rT   )r   r   r.   inputN)r1   message_indexr.   )r8   r   )r   r   clearr   r]   r_   r'   SandboxStdinWriter   SandboxStdinWriteRequestr^   r   ContainerExecPutInputContainerExecPutInputRequestRuntimeInputMessager   rc   r   )ro   r,   r   r   r2   r2   r3   drain  s*   


z _StreamWriterThroughServer.drainr   )r   r   r   r   r   r   r   rp   r   r   r   r   r   r   r{   r   r   r2   r2   r2   r3   r   y  s    	
	r   c                   @   sX   e Zd ZdedededdfddZdeeee	ef ddfd	d
Z
dddZdddZdS )!_StreamWriterThroughCommandRouterrQ   r   r   r"   Nc                 C   s*   || _ || _|| _d| _t | _d| _d S )NFr   )r^   _command_router_client_task_idr   r   r   _offset)ro   rQ   r   r   r2   r2   r3   rp     s   
z*_StreamWriterThroughCommandRouter.__init__r,   c                 C   sv   | j rtdt|ttttfr1t|tr|d}t| j	t| t
kr)td| j	| d S tdt|j )Nr   r%   r   r   r   r   r2   r2   r3   r{     s   

z'_StreamWriterThroughCommandRouter.writec                 C   s
   d| _ d S )NTr   rt   r2   r2   r3   r     r   z+_StreamWriterThroughCommandRouter.write_eofc                    sb   | j }| js	|r/t| j}| jj| j| j| j||dI d H  | j  |  jt	|7  _d S d S )N)r   r8   offsetr,   r.   )
r   r   r   r   exec_stdin_writer   r^   r  r   r   )ro   r.   r,   r2   r2   r3   r     s   


z'_StreamWriterThroughCommandRouter.drainr   )r   r   r   r   r   rp   r   r   r   r   r{   r   r   r2   r2   r2   r3   r     s    

r   c                   @   sv   e Zd ZdZ		ddeded dedee dee d	dfd
dZ	de
eeeef d	dfddZdddZdddZdS )_StreamWriterr   NrQ   rR   rS   r!   r   r   r"   c                 C   sF   |du rt |||| _dS |dusJ |dksJ t|||d| _dS )rY   NrU   )r   )r   r   r   )ro   rQ   rR   r!   r   r   r2   r2   r3   rp     s
   	z_StreamWriter.__init__r,   c                 C   s   | j | dS )a  Write data to the stream but does not send it immediately.

        This is non-blocking and queues the data to an internal buffer. Must be
        used along with the `drain()` method, which flushes the buffer.

        **Usage**

        ```python fixture:sandbox
        proc = sandbox.exec(
            "bash",
            "-c",
            "while read line; do echo $line; done",
        )
        proc.stdin.write(b"foo\n")
        proc.stdin.write(b"bar\n")
        proc.stdin.write_eof()
        proc.stdin.drain()
        ```
        N)r   r{   r   r2   r2   r3   r{      s   z_StreamWriter.writec                 C   s   | j   dS )r   N)r   r   rt   r2   r2   r3   r     s   z_StreamWriter.write_eofc                    s   | j  I dH  dS )a  Flush the write buffer and send data to the running process.

        This is a flow control method that blocks until data is sent. It returns
        when it is appropriate to continue writing data to the stream.

        **Usage**

        ```python notest
        writer.write(data)
        writer.drain()
        ```

        Async usage:
        ```python notest
        writer.write(data)  # not a blocking operation
        await writer.drain.aio()
        ```
        N)r   r   rt   r2   r2   r3   r     s   z_StreamWriter.drain)NNr   )r   r   r   r   r   r   r   r   r   rp   r   r   r   r   r{   r   r   r2   r2   r2   r3   r    s(    

	r  r   )DrA   r   r   rw   r   r?   collections.abcr   r   dataclassesr   typingr   r   r   r   r	   r
   r   r   grpclib.exceptionsr   modal.exceptionr   r   r   modal_protor   _utils.async_utilsr   r   r   !_utils.task_command_router_clientr   r!   r   configr   r   r   r   r   rV   r   r   r   r   r4   r   r   rM   rN   rO   ry   r   r   r   r   r   r   r   r   r   r   r   r  StreamReaderStreamWriterr2   r2   r2   r3   <module>   s   (

" "^"

"+1^G-H