
    ` IeEH                        d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
mZ  ej                  e      ZdZdZ G d de      Z G d	 d
      Zej(                  Zd Zee_         G d de      Z G d de      Z G d d      Zy)a  
Class that handles communications between Spyder kernel and frontend.

Comms transmit data in a list of buffers, and in a json-able dictionnary.
Here, we only support a buffer list with a single element.

The messages exchanged have the following msg_dict:

    ```
    msg_dict = {
        'spyder_msg_type': spyder_msg_type,
        'content': content,
    }
    ```

The buffer is generated by cloudpickle using `PICKLE_PROTOCOL = 2`.

To simplify the usage of messaging, we use a higher level function calling
mechanism:
    - The `remote_call` method returns a RemoteCallHandler object
    - By calling an attribute of this object, the call is sent to the other
      side of the comm.
    - If the `_wait_reply` is implemented, remote_call can be called with
      `blocking=True`, which will wait for a reply sent by the other side.

The messages exchanged are:
    - Function call (spyder_msg_type = 'remote_call'):
        - The content is a dictionnary {
            'call_name': The name of the function to be called,
            'call_id': uuid to match the request to a potential reply,
            'settings': A dictionnary of settings,
            }
        - The buffer encodes a dictionnary {
            'call_args': The function args,
            'call_kwargs': The function kwargs,
            }
    - If the 'settings' has `'blocking' =  True`, a reply is sent.
      (spyder_msg_type = 'remote_call_reply'):
        - The buffer contains the return value of the function.
        - The 'content' is a dict with: {
                'is_error': a boolean indicating if the return value is an
                            exception to be raised.
                'call_id': The uuid from above,
                'call_name': The function name (mostly for debugging)
                }
    )print_functionN)PY2PY3      c                       e Zd Zy)	CommErrorN)__name__
__module____qualname__     ?/usr/lib/python3/dist-packages/spyder_kernels/comms/commbase.pyr	   r	   J   s    r   r	   c                   2    e Zd Zd Zd Zd ZddZd Zd Zy)	CommsErrorWrapperc                     || _         || _        t        j                         \  | _        | _        }t        j                  |      | _        y N)		call_namecall_idsysexc_infoetypeerror	traceback
extract_tbtb)selfr   r   r   s       r   __init__zCommsErrorWrapper.__init__O   s8    "%(\\^"
DJ&&r*r   c                 $    | j                  |       )zL
        Raise the error while adding informations on the callback.
        )r   r   s    r   raise_errorzCommsErrorWrapper.raise_errorU   s    
 jjr   c                     dj                  | j                        gt        j                  | j                        z   t        j
                  | j                  | j                        z   }|S )zf
        Format the error received from the other side and returns a list of
        strings.
        zException in comms call {}:
)formatr   r   format_listr   format_exception_onlyr   r   )r   liness     r   format_errorzCommsErrorWrapper.format_error\   sW    
 288HI((12224::tzzJK r   Nc                 l    |t         j                  }| j                         D ]  }t        ||        y)zK
        Print the error to file or to sys.stderr if file is None.
        N)file)r   stderrr'   print)r   r)   lines      r   print_errorzCommsErrorWrapper.print_errorf   s4     <::D%%' 	#D$T"	#r   c                 ,    t        | j                        S )zGet string representation.)strr   r    s    r   __str__zCommsErrorWrapper.__str__o   s    4::r   c                 ,    t        | j                        S )z	Get repr.)reprr   r    s    r   __repr__zCommsErrorWrapper.__repr__s   s    DJJr   r   )	r
   r   r   r   r!   r'   r-   r0   r3   r   r   r   r   r   N   s     +# r   r   c                     t        |j                        dk(  rPt        |j                  d   t              r3t	        j
                  |       |j                  d   j                          y t        | ||       y )N   r   )lenargs
isinstancer   r   print_tbr-   sys_excepthook)typevaluer   s      r   comm_excepthookr=   |   sU    
5::!
5::a=:K L2

1!!#4#r   c                        e Zd ZdZ fdZddZddZddZddZd Z	ddZ
	 	 dd	Zd
 Zed        Zd Zd Zd Zd Zd Zd ZddZddZd Zd Zd Zd Zd Zd Zd Z xZS )CommBasez
    Class with the necessary attributes and methods to handle
    communications between a kernel and a frontend.
    Subclasses must open a comm and register it with `self._register_comm`.
    c                 &   t         t        |           d | _        i | _        i | _        i | _        i | _        i | _        | j                  d| j                         | j                  d| j                         | j                  d| j                         y )Nremote_callremote_call_reply_set_pickle_protocol)superr?   r   calling_comm_id_comms_message_handlers_remote_call_handlers_reply_inbox_reply_waitlist_register_message_handler_handle_remote_call_handle_remote_call_replyregister_call_handlerrC   )r   	__class__s    r   r   zCommBase.__init__   s    h&(#!#%'"!&&433	5&&!?!?	A""#9#'#<#<	>r   c                 Z    |%t        | j                  j                               }|S |g}|S )zGet a list of comms id.)listrF   keysr   comm_idid_lists      r   get_comm_id_listzCommBase.get_comm_id_list   s3    ?4;;++-.G  iGr   c                     | j                  |      }|D ]0  }	 | j                  |   d   j                          | j                  |= 2 y# t        $ r Y ?w xY w)z)Close the comm and notify the other side.commN)rV   rF   closeKeyErrorrS   s      r   rY   zCommBase.close   s`    ''0 	GG$V,224KK(	  s   -A	AAc                 R    |t        | j                        dkD  S || j                  v S )z!Check to see if the comm is open.r   )r6   rF   )r   rT   s     r   is_openzCommBase.is_open   s*    ?t{{#a''$++%%r   c                     | j                  |      }t        |      dk(  ryt        |D cg c]  }| j                  |   d   dk(   c}      S c c}w )z
        Check to see if the other side replied.

        The check is made with _set_pickle_protocol as this is the first call
        made. If comm_id is not specified, check all comms.
        r   Fstatusready)rV   r6   allrF   )r   rT   rU   cids       r   is_readyzCommBase.is_ready   sO     ''0w<1WMcDKK$X.'9MNNMs   Ac                 `    |s| j                   j                  |d       y|| j                   |<   y)a  
        Register a remote call handler.

        Parameters
        ----------
        call_name : str
            The name of the called function.
        handler : callback
            A function to handle the request, or `None` to unregister
            `call_name`.
        N)rH   pop)r   r   handlers      r   rN   zCommBase.register_call_handler   s0     &&**9d;07""9-r   c                     t        | ||fi |S )zGet a handler for remote calls.)RemoteCallFactory)r   rT   callbacksettingss       r   rA   zCommBase.remote_call   s     wEHEEr   c                 L   | j                  |      st        d      | j                  |      }|D ]r  }||| j                  |   d   t        j
                  d}t        j                  || j                  |   d         g}| j                  |   d   j                  ||       t y)a  
        Publish custom messages to the other side.

        Parameters
        ----------
        spyder_msg_type: str
            The spyder message type
        content: dict
            The (JSONable) content of the message
        data: any
            Any object that is serializable by cloudpickle (should be most
            things). Will arrive as cloudpickled bytes in `.buffers[0]`.
        comm_id: int
            the comm to send to. If None sends to all comms.
        The comm is not connected.pickle_protocol)spyder_msg_typecontentrl   python_version)protocolrX   )buffersN)	r\   r	   rV   rF   r   versioncloudpickledumpssend)r   rm   rn   datarT   rU   msg_dictrq   s           r   _send_messagezCommBase._send_message   s    " ||G$899''0 		IG#2"#';;w#78I#J"%++	H #((t{{734EFH IGKK (--h-H		Ir   c                     t        |t        j                        }|| j                  | j                     d<   d| j                  | j                     d<   y)z*Set the pickle protocol used to send data.rl   r_   r^   N)minpickleHIGHEST_PROTOCOLrF   rE   )r   rp   s     r   rC   zCommBase._set_pickle_protocol   sG    x!8!89?GD(()*;<6=D(()(3r   c                      y)z=
        Get the name used for the underlying comms.
        
spyder_apir   r    s    r   
_comm_namezCommBase._comm_name  s    
 r   c                 `    || j                   j                  |d       y|| j                   |<   y)a  
        Register a message handler.

        Parameters
        ----------
        message_id : str
            The identifier for the message
        handler : callback
            A function to handle the message. This is called with 3 arguments:
                - msg_dict: A dictionary with message information.
                - buffer: The data transmitted in the buffer
            Pass None to unregister the message_id
        N)rG   rd   )r   
message_idre   s      r   rK   z"CommBase._register_message_handler  s2     ?""&&z48-4z*r   c                     |j                  | j                         |j                  | j                         |t        dd| j
                  |j                  <   y)z0
        Open a new comm to the kernel.
        opening)rX   rl   r^   N)on_msg_comm_messageon_close_comm_closeDEFAULT_PICKLE_PROTOCOLrF   rT   )r   rX   s     r   _register_commzCommBase._register_comm  sE     	D&&'d&&'6%DLL!r   c                 .    |d   d   }| j                   |= y)zClose comm.rn   rT   N)rF   )r   msgrT   s      r   r   zCommBase._comm_close(  s    i.+KK r   c                    |d   d   | _         |d   d   }	 t        rt        j                  |d   d   d      }nt        j                  |d   d         }|d   }|| j                  v r | j                  |   ||       yt
        j                  d|z         y# t        $ rK}t
        j                  dt        |      z         t        |d   d	   |d   d
         }d|d   d<   Y d}~d}~ww xY w)z2
        Handle internal spyder messages.
        rn   rT   rv   rq   r   zlatin-1)encodingz#Exception in cloudpickle.loads : %sr   r   Tis_errorNrm   zNo such spyder message type: %s)
rE   r   rs   loads	Exceptionloggerdebugr/   r   rG   )r   r   rw   buffererm   s         r   r   zCommBase._comm_message-  s     #9~i8 y>&)	3
 %**3y>!+<4=? %**3y>!+<= ##45d4443D""?3&" LL:_LM  	3LL5A>@&#K0#I.0F /3HY
+	3s   ?B 	C-"AC((C-c                     |d   }| j                  |       	 | j                  |d   |d   |d         }| j                  ||       y# t        $ r) t	        |d   |d         }| j                  ||d       Y yw xY w)	zHandle a remote call.rn   r   	call_argscall_kwargsr   T)r   N)on_incoming_call_remote_callback_set_call_return_valuer   r   )r   r   r   rw   return_value	exc_infoss         r   rL   zCommBase._handle_remote_callR  s    y>h'		L00[);'=)+L '',? 	L)%x	':<I'')d'K	Ls   .A /A98A9c                 d    || j                   v r | j                   |   |i |S t        d|z        )z/Call the callback function for the remote call.zNo such spyder call type: %s)rH   r	   )r   r   r   r   s       r   r   zCommBase._remote_callbacka  sH    22284--i8+)+ + 6BCCr   c                     |d   }d|v xr |d   }|r|r|j                          d|v xr |d   }|sy||d   |d   d}| j                  d||| j                  	       y)
zq
        A remote call has just been processed.

        This will reply if settings['blocking'] == True
        ri   display_error
send_replyNr   r   )r   r   r   rB   rn   rv   rT   )r-   rx   rE   )r   	call_dictrv   r   ri   r   r   rn   s           r   r   zCommBase._set_call_return_valuei  s     Z((H4 3!/2 	!X-H(<2H
  +";/
 	.d#'#7#7 	 	9r   c                 Z    |d   }d|v xr |d   }|d   }|s|||f| j                   |<   yy)zI
        Register the call so the reply can be properly treated.
        ri   blockingr   N)rJ   )r   r   rh   ri   r   r   s         r   _register_callzCommBase._register_call  sN     Z()Bhz.BI&x+,4h,>D  ) ,r   c                 ,    t         j                  |d<   |S )zA message is about to be sentpickle_highest_protocol)r{   r|   r   r   s     r   on_outgoing_callzCommBase.on_outgoing_call  s    /5/F/F	+,r   c                 6    d|v r| j                  |d          yy)zA call was receivedr   N)rC   r   s     r   r   zCommBase.on_incoming_call  s#    $	1%%i0I&JK 2r   c                 J   | j                  |      }| j                  d|||       |d   }d|v xr |d   }|sy|d   }|d   }d|v r|d   |d   }nt        }| j                  |||       | j                  j                  |      }	|	d	   r| j                  |	d
         S |	d
   S )z
        Send a remote call and return the reply.

        If settings['blocking'] == True, this will wait for a reply and return
        the replied value.
        rA   r   ri   r   Nr   r   timeoutr   r<   )r   rx   TIMEOUT_wait_replyrI   rd   _sync_error)
r   r   	call_datarT   ri   r   r   r   r   replys
             r   _get_call_return_valuezCommBase._get_call_return_value  s     )))4	99 	 	 Z()Bhz.BI&k*	  Xi%8%Dy)GG)W5!!%%g.##E'N33W~r   c                     t         )z0
        Wait for the other side reply.
        NotImplementedError)r   r   r   r   s       r   r   zCommBase._wait_reply  s
     "!r   c                 d   |d   }|d   }|d   }|d   }|| j                   vr9|r| j                  |      S t        j                  dj	                  ||             y| j                   j                  |      \  }}|r|s| j                  |      S |
|s ||       |r|||d| j                  |<   yy)z3
        A blocking call received a reply.
        rn   r   r   r   z!Got an unexpected reply {}, id:{}N)r   r<   rn   )rJ   _async_errorr   r   r#   rd   rI   )	r   rw   r   rn   r   r   r   r   rh   s	            r   rM   z"CommBase._handle_remote_call_reply  s     9%)$K(	:& $...((00@GGw( )!1155g>( H$$V,, V  (#&*Dg& r   c                 $    |j                          y)zR
        Handle an error that was raised on the other side asyncronously.
        N)r-   r   error_wrappers     r   r   zCommBase._async_error       	!!#r   c                 $    |j                          y)zQ
        Handle an error that was raised on the other side syncronously.
        N)r!   r   s     r   r   zCommBase._sync_error  r   r   r   )NN)NNN)F)r
   r   r   __doc__r   rV   rY   r\   rb   rN   rA   rx   rC   propertyr   rK   r   r   r   rL   r   r   r   r   r   r   r   rM   r   r   __classcell__rO   s   @r   r?   r?      s    >$	&
O8$F
 AE"I>>  5(
!
#NJLD94?
L
#J""H$$r   r?   c                   .     e Zd ZdZ fdZd Zd Z xZS )rg   zClass to create `RemoteCall`s.c                     t         t        |   d|       t         t        |   d|       t         t        |   d|       t         t        |   d|       y )N_comms_wrapper_comm_id	_callback	_settings)rD   rg   __setattr__)r   comms_wrapperrT   rh   ri   rO   s        r   r   zRemoteCallFactory.__init__  sN    2m	-2:wG2;I2;Ir   c                 p    t        || j                  | j                  | j                  | j                        S )z'Get a call for a function named 'name'.)
RemoteCallr   r   r   r   )r   names     r   __getattr__zRemoteCallFactory.__getattr__  s,    $ 3 3T]]..$..: 	:r   c                     t         )z#Set an attribute to the other side.r   )r   r   r<   s      r   r   zRemoteCallFactory.__setattr__  s    !!r   )r
   r   r   r   r   r   r   r   r   s   @r   rg   rg     s    (J:
"r   rg   c                       e Zd ZdZd Zd Zy)r   z:Class to call the other side of the comms like a function.c                 J    || _         || _        || _        || _        || _        y r   )_namer   r   r   r   )r   r   r   rT   rh   ri   s         r   r   zRemoteCall.__init__  s&    
+!!r   c                 <   d| j                   v xr | j                   d   }|xs | j                  du| j                   d<   t        j                         j                  }| j
                  || j                   d}||d}| j                  j                  | j                        s0|rt        d      t        j                  d| j
                  z         y| j                  j                  || j                         | j                  j                  ||| j                        S )zw
        Transmit the call to the other side of the tunnel.

        The args and kwargs have to be picklable.
        r   Nr   )r   r   ri   )r   r   rk   zCall to unconnected comm: %s)r   r   uuiduuid4hexr   r   r\   r   r	   r   r   r   r   )r   r7   kwargsr   r   r   r   s          r   __call__zRemoteCall.__call__  s     /NDNN:4N'/'M4>>3M|$**,""	 !	
 ""**4==9 <==LL7$**DE**9dnnE""99y$--1 	1r   N)r
   r   r   r   r   r   r   r   r   r   r     s    D"1r   r   )r   
__future__r   rs   r{   loggingr   r   r   spyder_kernels.py3compatr   r   	getLoggerr
   r   r   r   RuntimeErrorr	   r   
excepthookr:   r=   objectr?   rg   r   r   r   r   <module>r      s   -\ &    
   - 
		8	$   	 	'  ' V $ !i$v i$X" "*&1 &1r   