U
    ZfG                     @   s$  d dl Z d dlZd dlZd dlZd dlZd dlZd dlZd dl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Zd dlmZmZmZmZ d dlmZ eeZdddZG dd dZG d	d
 d
ejZG dd deZG dd deZG dd deZG dd deZ G dd deZ!dS )    N)NoAppFoundErrorTestingTimeoutErrorServerCloseErrorDashAppLoadingError)waitappc              
   C   sX   zt | }|| }W n< tk
rR } ztd td|  |W 5 d}~X Y nX |S )a
  Import a dash application from a module. The import path is in dot
    notation to the module. The variable named app will be returned.

    :Example:

        >>> app = import_app("my_app.app")

    Will import the application in module `app` of the package `my_app`.

    :param app_file: Path to the app (dot-separated).
    :type app_file: str
    :param application_name: The name of the dash application instance.
    :raise: dash_tests.errors.NoAppFoundError
    :return: App from module.
    :rtype: dash.Dash
    zthe app name cannot be foundz$No dash `app` instance was found in N)runpyZ
run_moduleKeyErrorlogger	exceptionr   )Zapp_fileapplication_name
app_moduler   Zapp_name_missing r   D/tmp/pip-unpacked-wheel-47crqvv_/dash/testing/application_runners.py
import_app   s    

r   c                   @   st   e Zd ZdZdZdd Zdd Zdd Zed	d
 Z	dd Z
dd Zdd Zedd Zedd Zedd ZdS )BaseDashRunnerz4Base context manager class for running applications.i  c                 C   s"   d| _ d | _|| _|| _d | _d S )Nr  )portstarted	keep_openstop_timeout_tmp_app_pathselfr   r   r   r   r   __init__@   s
    zBaseDashRunner.__init__c                 O   s   t d S NNotImplementedErrorr   argskwargsr   r   r   startG   s    zBaseDashRunner.startc                 C   s   t d S r   r   r   r   r   r   stopJ   s    zBaseDashRunner.stopc                 C   s.   zt |  W n t jjk
r(   Y dS X dS )NFT)requestsget
exceptionsRequestException)urlr   r   r   
accessibleM   s
    zBaseDashRunner.accessiblec                 O   s   | j ||S r   )r!   r   r   r   r   __call__U   s    zBaseDashRunner.__call__c                 C   s   | S r   r   r"   r   r   r   	__enter__X   s    zBaseDashRunner.__enter__c              
   C   sh   | j rZ| jsZztd |   W n6 tk
rX } ztd| j d|W 5 d }~X Y nX td d S )Nzkilling the app runnerzCannot stop server within z	s timeoutz__exit__ complete)r   r   r
   infor#   r   r   r   )r   exc_typeexc_val	tracebackZcannot_stop_serverr   r   r   __exit__[   s    
zBaseDashRunner.__exit__c                 C   s   d| j  S )zThe default server url.zhttp://localhost:)r   r"   r   r   r   r(   f   s    zBaseDashRunner.urlc                 C   s
   t jdkS )Nwin32)sysplatformr"   r   r   r   
is_windowsk   s    zBaseDashRunner.is_windowsc                 C   s   | j S r   )r   r"   r   r   r   tmp_app_patho   s    zBaseDashRunner.tmp_app_pathN)__name__
__module____qualname____doc__
_next_portr   r!   r#   staticmethodr)   r*   r+   r0   propertyr(   r4   r5   r   r   r   r   r   ;   s    


r   c                       s$   e Zd Z fddZdd Z  ZS )KillerThreadc                    s"   t  jf | ttj | _d S r   )superr   list	threading_activekeys_old_threads)r   r    	__class__r   r   r   u   s    zKillerThread.__init__c                 C   st   t tjD ]d}|| jkrq
tjt|tt	}|dkrJt
d| |dkr
tjt|d  t	dq
d S )Nr   zInvalid thread id:    zStopping thread failure)r?   r@   rA   rC   ctypes	pythonapiZPyThreadState_SetAsyncExcc_long	py_object
SystemExit
ValueError)r   Z	thread_idresr   r   r   killy   s    
  zKillerThread.kill)r6   r7   r8   r   rN   __classcell__r   r   rD   r   r=   t   s   r=   c                       s<   e Zd ZdZd fdd	Zdd Zddd	Zd
d Z  ZS )ThreadedRunnerzkRuns a dash application in a thread.

    This is the default flavor to use in dash integration tests.
    F   c                    s   t  j||d d | _d S N)r   r   )r>   r   threadr   rD   r   r   r      s    zThreadedRunner.__init__c                 C   s    | j  r| |S tdd S )NzThread is not alive.)rS   is_aliver)   r   )r   r(   r   r   r   running_and_accessible   s    

z%ThreadedRunner.running_and_accessiblec              
      s    fdd}d}j s|dk rzhjrHj r>  n
j  t|d_dj_j  tj	fdd|d	 j _ W q t
k
r } z&t| d
_ |d7 }td W 5 d}~X Y qX qj _ j stddS )z)Start the app server in threading flavor.c               
      s   d j j_d jj_ } dkrDtj | d< _t jd7  _n
| d _z jf ddi|  W nL t	k
r   t
d Y n0 tk
r } zt
| |W 5 d }~X Y nX d S )NTr   rF   threadedServer stopped)scriptsconfigserve_locallycsscopyr   r:   r   runrK   r
   r,   	Exceptionr   optionserrorr   r    r   r   r   r]      s    



z!ThreadedRunner.start.<locals>.runr   rQ   targetTc                      s      jS r   )rU   r(   r   r"   r   r   <lambda>       z&ThreadedRunner.start.<locals>.<lambda>timeoutFrF   Nzthreaded server failed to start)r   rS   rT   r#   rN   r=   daemonr!   r   untilr^   r
   r   timesleepr   )r   r   start_timeoutr    r]   retrieserrr   rb   r   r!      s0    




 
zThreadedRunner.startc                 C   s0   | j   | j   t| j j| j d| _d S )NF)rS   rN   joinr   Z	until_notrT   r   r   r"   r   r   r   r#      s    

zThreadedRunner.stop)FrQ   )rQ   )	r6   r7   r8   r9   r   rU   r!   r#   rO   r   r   rD   r   rP      s
   
3rP   c                       s0   e Zd Zd	 fdd	Zd
ddZdd Z  ZS )MultiProcessRunnerFrQ   c                    s   t  || d | _d S r   r>   r   procr   rD   r   r   r      s    zMultiProcessRunner.__init__c                    sT    dd_ fdd}tj|d_j  tjfdd|d d	_d S )
Nr   r   c               
      s   d j j_d jj_ } z jf ddi|  W nN tk
rT   td  Y n0 t	k
r } zt
| |W 5 d }~X Y nX d S )NTrV   rW   )rX   rY   rZ   r[   r\   r]   rK   r
   r,   r^   r   r_   )r   r    r   r   rd      s    



z(MultiProcessRunner.start.<locals>.targetrc   c                      s      jS r   r)   r(   r   r"   r   r   re      rf   z*MultiProcessRunner.start.<locals>.<lambda>rg   T)	r%   r   multiprocessProcessrs   r!   r   rj   r   )r   r   rm   r    rd   r   rb   r   r!      s    
zMultiProcessRunner.startc              	   C   s   t | jj}|jddD ](}z|  W q t jk
r@   Y qX qz|  W n t jk
rf   Y nX z|d W n t jt jfk
r   Y nX d S )NT)	recursiverF   )	psutilrv   rs   pidchildrenrN   ZNoSuchProcessr   TimeoutExpired)r   processrs   r   r   r   r#      s    zMultiProcessRunner.stop)FrQ   )rQ   )r6   r7   r8   r   r!   r#   rO   r   r   rD   r   rq      s   
rq   c                       s4   e Zd ZdZd fdd	Zdd	d
Zdd Z  ZS )ProcessRunnerz}Runs a dash application in a waitress-serve subprocess.

    This flavor is closer to production environment but slower.
    FrQ   c                    s   t  j||d d | _d S rR   rr   r   rD   r   r   r     s    zProcessRunner.__init__Nr   r   c              	      s   |s|st d dS | _tj|r(|nd| d| d| d j d}td| z0tj	|tj
tj
d	 _tj fd
d|d W n2 ttfk
r   td d _   Y dS X d _dS )z7Start the server with waitress-serve in process flavor.zAthe process runner needs to start with at least one valid commandNz waitress-serve --listen=0.0.0.0: :z.serverposixstart dash process with %s)stdoutstderrc                      s      jS r   rt   r   r"   r   r   re   /  rf   z%ProcessRunner.start.<locals>.<lambda>rg   'process server has encountered an errorFT)loggingra   r   shlexsplitr4   r
   debug
subprocessPopenPIPErs   r   rj   OSErrorrL   r   r   r#   )r   r   r   Zraw_commandr   rm   r   r   r"   r   r!     s4    	  
zProcessRunner.startc                 C   s   | j rzbtd| j j | j   | jrPtj| jrPt	d| j t
| j tj}| j j| jd W n2 |k
r   td | j   | j   Y nX td d S )Nzproc.terminate with pid %szremoving temporary app path %srg   zPsubprocess terminate not success, trying to kill the subprocess in a safe mannerzprocess stop completes!)rs   r
   r,   ry   	terminater5   ospathexistsr   shutilrmtreer   r{   communicater   r   rN   )r   Z_exceptr   r   r   r#   9  s$    


zProcessRunner.stop)FrQ   )Nr   Nr   rQ   )r6   r7   r8   r9   r   r!   r#   rO   r   r   rD   r   r}     s        
'r}   c                       s(   e Zd Zd	 fdd	Zd
ddZ  ZS )RRunnerFrQ   c                    s   t  j||d d | _d S rR   rr   r   rD   r   r   r   Q  s    zRRunner.__init__   Nc              	      s  t j|r8t j|r8 s4t j| td  nt jjsHdnt 	dt
 j_zt j W n" tk
r   tdj Y nX t jjd}td td| td td	| t|d
dd}|| W 5 Q R X |} sLt D ]F}d|d ddkrt jt j|d  td   qLq rtd   fddt  D }|D ]p}t jjt j|}	t j|	rtd|	 t|	 td|j t||	 tdt |	 qzn
td td| tj dt j| dj d}
td|
 z@t!j"|
t!j#t!j#jrPjn d_$t%j&fdd |d! W n, tt'fk
r   td" d#_(Y d$S X d%_(d$S )&z-Start the server with subprocess and Rscript.z&RRunner inferred cwd from app path: %s/tmpTEMPcannot make temporary folder %szapp.Rz$RRunner start => app is R code chunkz+make a temporary R file for execution => %szcontent of the dashR app%swutf-8encoding/dash/testing/rF   \/get cwd from inspect => %sz3RRunner inferred cwd from the Python call stack: %sc                    s:   g | ]2}| d stjtj |rtj |qS __
startswithr   r   isdirrp   .0_cwdr   r   
<listcomp>  s   
 z!RRunner.start.<locals>.<listcomp>delete existing target %scopying %s => %scopied with %szRRunner found no cwd in the Python call stack. You may wish to specify an explicit working directory using something like: dashr.run_server(app, cwd=os.path.dirname(__file__))z Run dashR app with Rscript => %szRscript -e 'source("z")'r   r   r   r   r   c                      s      jS r   rt   r   r"   r   r   re     rf   zRRunner.start.<locals>.<lambda>rg   r   FNT)r   r   isfiler   dirnamer
   r,   rp   r4   getenvuuidZuuid4hexr   mkdirr5   r   r   r   openwriteinspectstackreplacerealpathwarninglistdirbasenamer   r   copytreer   r   r   r   r   rs   r   rj   rL   r   r   r   rm   r   r   fpentryZassetsZassetrd   r   r   r   r   r   r!   V  sz     





zRRunner.start)FrQ   )r   Nr6   r7   r8   r   r!   rO   r   r   rD   r   r   P  s   r   c                       s(   e Zd Zd	 fdd	Zd
ddZ  ZS )JuliaRunnerFrQ   c                    s   t  j||d d | _d S rR   rr   r   rD   r   r   r     s    zJuliaRunner.__init__   Nc              	      s  t j|r8t j|r8 s4t j| td  nt jjsHdnt 	dt
 j_zt j W n" tk
r   tdj Y nX t jjd}td td| td td	| t|d
dd}|| W 5 Q R X |} sLt D ]F}d|d ddkrt jt j|d  td   qLq rtd   fddt  D }|D ]p}t jjt j|}	t j|	rtd|	 t|	 td|j t||	 tdt |	 qzn
td td| tj dt j| j d}
td|
 z@t!j"|
t!j#t!j#jrNjn d_$t%j&fdd|d  W n, tt'fk
r   td! d"_(Y d#S X d$_(d#S )%z+Start the server with subprocess and julia.z*JuliaRunner inferred cwd from app path: %sr   r   r   zapp.jlz,JuliaRunner start => app is Julia code chunkz/make a temporary Julia file for execution => %szcontent of the Dash.jl appr   r   r   r   r   rF   r   r   r   z7JuliaRunner inferred cwd from the Python call stack: %sc                    s:   g | ]2}| d stjtj |rtj |qS r   r   r   r   r   r   r     s   
 z%JuliaRunner.start.<locals>.<listcomp>r   r   r   zJuliaRunner found no cwd in the Python call stack. You may wish to specify an explicit working directory using something like: dashjl.run_server(app, cwd=os.path.dirname(__file__))z Run Dash.jl app with julia => %szjulia --project r   zstart Dash.jl process with %sr   c                      s      jS r   rt   r   r"   r   r   re   	  rf   z#JuliaRunner.start.<locals>.<lambda>rg   r   FNTr   r   r   r   r   r!     s     


 

 
zJuliaRunner.start)FrQ   )r   Nr   r   r   rD   r   r     s   r   )r   )"r2   r   rk   r   r   r@   r   r   r   r   rG   r   r$   rx   ru   Zdash.testing.errorsr   r   r   r   Zdash.testingr   	getLoggerr6   r
   r   r   Threadr=   rP   rq   r}   r   r   r   r   r   r   <module>   s2   

9J2Ia