o
    [hg                     @   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mZ d dl	m
Z
 d dlmZ d dlZd dlm  mZ d dlZd dlmZ d dlmZmZ d dlmZmZmZmZm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%m&Z&m'Z'm(Z( d dl)m*Z* d dl+m,Z,m-Z- d dl.m/Z/m0Z0m1Z1m2Z2 d dl3m4Z4m5Z5m6Z6 d dlm7Z7 d dl8m9Z9 d dlm:Z:m;Z;m<Z< d dl=m>Z> d dl?Z?e@eAZBddiZCG dd deDZEG dd deZFG dd deFZGG dd deGZHG dd  d eGZIG d!d" d"eFZJG d#d$ d$eFZKG d%d& d&eFZLG d'd( d(eFZMG d)d* d*eMZNG d+d, d,eMZOG d-d. d.eFZPG d/d0 d0eFZQG d1d2 d2eFZRG d3d4 d4eFZSG d5d6 d6eFZTG d7d8 d8eFZUG d9d: d:eFZVG d;d< d<eFZWd=eXfd>d?ZYG d@dA dAZZdS )B    N)WebSocketEndpoint)	WebSocket)FormData)settings)get_chat_groupchannel_layer)get_models_moduleGlobalStatesigner_signsigner_unsignlock
json_dumps)NoResultFound)dbsession_scope)dbq)export_wide
export_appcustom_export_appBOM)live_payload_function)ParticipantSession)CompletedGroupWaitPageCompletedSubsessionWaitPageCompletedGBATWaitPageChatMessage)	ROOM_DICT	LabelRoomNoLabelRoom)SESSION_CONFIGS_DICT)CreateSessionForm)CSRF_TOKEN_NAMEAUTH_COOKIE_NAMEAUTH_COOKIE_VALUE)lock2statusZsession_readyc                   @   s   e Zd ZdZdS )InvalidWebSocketParamsz4exception to raise when websocket params are invalidN)__name__
__module____qualname____doc__ r-   r-   /home/ubuntu/experiments/live_experiments/Pythonexperiments/Otree/venv/lib/python3.10/site-packages/otree/channels/consumers.pyr(   2   s    r(   c                       s   e Zd ZU dZdZeed< eed< dZdd Z	dd	 Z
 fd
dZdd ZdeddfddZdd ZdedefddZdd ZdefddZdd Zdd Z  ZS ) _OTreeAsyncJsonWebsocketConsumerz;
    This is not public API, might change at any time.
    json	websocketgroupsFc                 K   s   |S )a  
        subclasses should override if the route accesses query params.
        otherwise, this just passes the route kwargs as is (usually there is just one).
        The output of this method is passed to self.group_name(), self.post_connect,
        and self.pre_disconnect, so within each class, all 3 of those methods must
        accept the same args (or at least take a **kwargs wildcard, if the args aren't used)
        r-   selfkwargsr-   r-   r.   clean_kwargs@   s   z-_OTreeAsyncJsonWebsocketConsumer.clean_kwargsc                 K   s   t  N)NotImplementedErrorr3   r-   r-   r.   
group_nameJ   s   z+_OTreeAsyncJsonWebsocketConsumer.group_namec                    sV   t  j|i | | jdi | jd | _| jdi | j}|r&|g| _d S g | _d S )NZpath_paramsr-   )super__init__r6   scopecleaned_kwargsr9   r2   )r4   argsr5   r9   	__class__r-   r.   r;   M   s   z)_OTreeAsyncJsonWebsocketConsumer.__init__c                 C      d S r7   r-   r4   r-   r-   r.   _is_unauthorizedS      z1_OTreeAsyncJsonWebsocketConsumer._is_unauthorizedreturnNc              	      s   t |j|_| I d H  | jr2|jttks2d	| j
d }t| |jddI d H  d S || _t4 I d H * t  | jdi | jI d H  W d    n1 sVw   Y  W d   I d H  n1 I d H skw   Y  | jD ]}t|| qsd S )Nz5rejected un-authenticated access to websocket path {}pathi  coder-   )channel_utilsZwrap_websocket_sendsendaccept_requires_loginsessiongetr$   r%   formatr<   loggererrorcloser1   r&   r   post_connectr=   r2   r   add)r4   r1   msggroupr-   r-   r.   
on_connectV   s*   
(
z+_OTreeAsyncJsonWebsocketConsumer.on_connectc                       d S r7   r-   r3   r-   r-   r.   rS   o      z-_OTreeAsyncJsonWebsocketConsumer.post_connect
close_codec              	      s   t 4 I d H * t  | jdi | jI d H  W d    n1 s"w   Y  W d   I d H  n1 I d H s7w   Y  | jD ]}t|| q?d S Nr-   )r&   r   pre_disconnectr=   r2   r   discard)r4   r1   rZ   rV   r-   r-   r.   on_disconnectr   s   (
z._OTreeAsyncJsonWebsocketConsumer.on_disconnectc                    rX   r7   r-   r3   r-   r-   r.   r\   y   rY   z/_OTreeAsyncJsonWebsocketConsumer.pre_disconnectc              	      s   t 4 I d H , t  | j|fi | jI d H  W d    n1 s#w   Y  W d   I d H  d S 1 I d H s9w   Y  d S r7   )r&   r   post_receive_jsonr=   )r4   r1   datar-   r-   r.   
on_receive|   s   .z+_OTreeAsyncJsonWebsocketConsumer.on_receivec                    rX   r7   r-   r4   contentr5   r-   r-   r.   r_      rY   z2_OTreeAsyncJsonWebsocketConsumer.post_receive_jsonc                    s   | j |I d H  d S r7   )r1   	send_json)r4   r`   r-   r-   r.   rd      s   z*_OTreeAsyncJsonWebsocketConsumer.send_json)r)   r*   r+   r,   encodingr   __annotations__listrL   r6   r9   r;   rC   rW   rS   intr^   r\   ra   r_   rd   __classcell__r-   r-   r?   r.   r/   6   s"   
 
r/   c                   @   s   e Zd ZU eed< dd ZdS )BaseWaitPagekwarg_namesc                 C   s2   t | jd }i }| jD ]
}t|| ||< q|S Nquery_string)parse_querystringr<   rk   rh   )r4   dr5   kr-   r-   r.   r6      s
   
zBaseWaitPage.clean_kwargsN)r)   r*   r+   rg   rf   r6   r-   r-   r-   r.   rj      s   
 rj   c                   @   s(   e Zd ZdZdd Zdd Zdd ZdS )	WSSubsessionWaitPage)
session_pk
page_indexparticipant_idc                 C   s   t ||S r7   )rI   Zsubsession_wait_page_namer4   rr   rs   rt   r-   r-   r.   r9      s   zWSSubsessionWaitPage.group_namec                 K      t jdi |S r[   )r   objects_existsr3   r-   r-   r.   completion_exists      z&WSSubsessionWaitPage.completion_existsc                    s.   | j ||dr| jddiI d H  d S d S )N)rs   
session_idr'   readyrx   r1   rd   ru   r-   r-   r.   rS      s   z!WSSubsessionWaitPage.post_connectN)r)   r*   r+   rk   r9   rx   rS   r-   r-   r-   r.   rq      s
    rq   c                   @   s.   e Zd Zejd Zdd Zdd Zdd ZdS )	WSGroupWaitPage)group_idc                 C      t |||S r7   )rI   Zgroup_wait_page_namer4   rr   rs   r~   rt   r-   r-   r.   r9         zWSGroupWaitPage.group_namec                 K   rv   r[   )r   rw   r3   r-   r-   r.   rx      ry   z!WSGroupWaitPage.completion_existsc                    s0   | j |||dr| jddiI d H  d S d S )N)rs   r~   rz   r'   r{   r|   r   r-   r-   r.   rS      s   zWSGroupWaitPage.post_connectN)r)   r*   r+   rq   rk   r9   rx   rS   r-   r-   r-   r.   r}      s
    
r}   c                   @   s8   e Zd Zdd Zdd Zdd Zdd Zed	d
 ZdS )LiveConsumerc                 K   r   r7   )rI   Z
live_group)r4   session_coders   participant_coder5   r-   r-   r.   r9      r   zLiveConsumer.group_namec                 C   s   t | jd S rl   )rn   r<   rB   r-   r-   r.   r6      r   zLiveConsumer.clean_kwargsc                 C   s   t j|ddS )NT)rH   Zis_browser_bot)r   rw   )r4   r   r-   r-   r.   browser_bot_exists   s   zLiveConsumer.browser_bot_existsc                    s(   |  |rd S t|||dI d H  d S )N)r   	page_namepayload)r   r   )r4   rc   r   r   r5   r-   r-   r.   r_      s   
zLiveConsumer.post_receive_jsonc                    s
   t |S r7   r   )clsrc   r-   r-   r.   encode_json   s   zLiveConsumer.encode_jsonN)	r)   r*   r+   r9   r6   r   r_   classmethodr   r-   r-   r-   r.   r      s    r   c                   @   s^   e Zd ZU eed< eed< dd Zdd Zdd Zd	d
 Z	dd Z
dd Zdd Zdd ZdS )WSGroupByArrivalTimeapp_name	player_idc                 C   sB   t | jd }|d t|d t|d t|d t|d dS )Nrm   r   rr   rt   rs   r   )r   rr   rt   rs   r   rn   r<   rh   r4   ro   r-   r-   r.   r6      s   



z!WSGroupByArrivalTime.clean_kwargsc                 C   s   t ||}|S r7   )rI   Zgbat_group_name)r4   r   r   rs   rr   rt   Zgnr-   r-   r.   r9      s   zWSGroupByArrivalTime.group_namec          	      C   sL   t |}|j}|j}t|||j|k|j	 \}t
j|||dS )N)rs   id_in_subsessionrz   )r   PlayerGroupr   joinfilteridwith_entitiesr   oner   rw   )	r4   r   r   rs   rr   Zmodels_moduler   r   Zgroup_id_in_subsessionr-   r-   r.   is_ready   s   zWSGroupByArrivalTime.is_readyc                 C      t j| jdt j|i d S N)r   )r   objects_filterrt   updateZ_gbat_is_connected)r4   Zis_connectedr-   r-   r.   mark_gbat_is_connected      z+WSGroupByArrivalTime.mark_gbat_is_connectedc                 C   r   r   )r   r   rt   r   Z_gbat_tab_hidden)r4   
tab_hiddenr-   r-   r.   mark_gbat_tab_hidden   r   z)WSGroupByArrivalTime.mark_gbat_tab_hiddenc                    sf   || _ || _|| _z| j||||d}W n	 ty   Y nw |r,| jddiI d H  | d d S )N)r   r   rs   rr   r'   r{   T)r   r   rt   r   r   r1   rd   r   )r4   r   r   rs   rr   rt   r   r-   r-   r.   rS      s"   
z!WSGroupByArrivalTime.post_connectc                    s   |  d d S )NF)r   )r4   r   r   rs   rr   rt   r-   r-   r.   r\     s   z#WSGroupByArrivalTime.pre_disconnectc                    s    d|v r|  |d  d S d S )Nr   )r   rb   r-   r-   r.   r_     s   z&WSGroupByArrivalTime.post_receive_jsonN)r)   r*   r+   strrf   rh   r6   r9   r   r   r   rS   r\   r_   r-   r-   r-   r.   r      s   
 
r   c                   @   ,   e Zd Zdd Zdd Zdd Zdd Zd	S )
DetectAutoAdvancec                 C   s$   t | jd }|d t|d dS )Nrm   r   rs   )r   rs   r   r   r-   r-   r.   r6      s   
zDetectAutoAdvance.clean_kwargsc                 C   s
   t |S r7   )rI   Zauto_advance_group)r4   rs   r   r-   r-   r.   r9   '     
zDetectAutoAdvance.group_namec                 C   s4   zt j|dd \}|W S  ty   Y d S w )NrG   Z_index_in_pages)r   r   r   r   r   )r4   r   resr-   r-   r.   page_should_be_on*  s   
z#DetectAutoAdvance.page_should_be_onc                    sP   |  |}|d u r| ddiI d H  d S ||kr&| ddiI d H  d S d S )NrQ   z"Participant not found in database.Zauto_advancedT)r   rd   )r4   rs   r   r   r-   r-   r.   rS   5  s   
zDetectAutoAdvance.post_connectN)r)   r*   r+   r6   r9   r   rS   r-   r-   r-   r.   r     s
    r   c                   @   s*   e Zd Zdd ZdefddZdd ZdS )	BaseCreateSessionc                 K   rA   r7   r-   r3   r-   r-   r.   r9   @  rD   zBaseCreateSession.group_nameeventc                    s   t r7   )NotImplementedr4   r   r-   r-   r.   send_response_to_browserC  rY   z*BaseCreateSession.send_response_to_browserc              
      s   zt jjdi |}|rt jjj|jd d n|jr|  W n9 t	yY } z-t
|t jjr2|j}dtt|||j}| td| |dI d H  W Y d }~d S d }~ww ddlm} |jrednd}| d	|||jd
iI d H  d S )N)rr   Zcase_number zFailed to create session: )rQ   	tracebackr   )reverseZMTurkCreateHITZSessionStartLinksZsession_urlrG   r-   )otreerM   Z create_session_traceback_wrapperZbotsZbrowserZinitialize_sessionr   is_demoZmock_exogenous_data	Exception
isinstanceZCreateSessionError	__cause__r   r   format_exceptiontype__traceback__r   dictZ
otree.asgir   is_mturkrH   )r4   use_browser_botsZsession_kwargsrM   eZtraceback_strr   Zsession_home_viewr-   r-   r.   #create_session_then_send_start_linkF  s6   z5BaseCreateSession.create_session_then_send_start_linkN)r)   r*   r+   r9   r   r   r   r-   r-   r-   r.   r   ?  s    r   c                   @   s(   e Zd ZdefddZdefddZdS )WSCreateDemoSessionr   c                    s   |  |I d H  d S r7   )rd   r   r-   r-   r.   r   x  s   z,WSCreateDemoSession.send_response_to_browser	form_datac                    sl   |d }t |}|sd| d}| d|iI d H  d S |d }|dd}| j|||dd	I d H  d S )
Nsession_configzSession config "z" does not exist.validation_errorsZnum_demo_participantsr   FT)session_config_namer   num_participantsr   )r!   rN   rd   r   )r4   r   r   configrU   r   r   r-   r-   r.   r_   {  s   
z%WSCreateDemoSession.post_receive_jsonN)r)   r*   r+   r   r   r_   r-   r-   r-   r.   r   w  s    r   c                   @   s0   e Zd Zdd ZdefddZdefddZd	S )
WSCreateSessionc                 K      dS )NZcreate_sessionr-   r3   r-   r-   r.   r9     rD   zWSCreateSession.group_namer   c              	      s\  t t|d}| s| d|jiI d H  d S |jj}|jj}t| }|j	j}|r0|t
j9 }i }| D ]E}||}	|| }
t|
trVt||	}|
|krU|||< q6||	d}|dkr{t|
tu rmtt|}nt|
|}|
|kr{|||< q6|d|dd}|jjpd }| j||d||||dI d H  |rtjt|tdI d H  d S d S )N)Zformdatar   r   r   F)r   r   r   r   modified_session_config_fieldsr   	room_namerV   r`   )r"   r   validaterd   errorsr   r`   r   r!   r   r   ZMTURK_NUM_PARTICIPANTS_MULTIPLEZeditable_fieldshtml_field_namer   boolrN   r   rh   floatr   r   rI   
group_sendroom_participants_group_nameSESSION_READY_PAYLOAD)r4   r   formr   r   r   r   r   fieldr   	old_value	new_valueZnew_value_rawr   r   r-   r-   r.   r_     s^   



z!WSCreateSession.post_receive_jsonr   c                    s"   | j \}tj||dI dH  dS )a  
        Send to a group instead of the channel only,
        because if the websocket disconnects during creation of a large session,
        (due to temporary network error, etc, or Heroku H15, 55 seconds without ping)
        the user could be stuck on "please wait" forever.
        the downside is that if two admins create sessions around the same time,
        your page could automatically redirect to the other admin's session.
        r   N)r2   rI   r   )r4   r   rV   r-   r-   r.   r     s   	z(WSCreateSession.send_response_to_browserN)r)   r*   r+   r9   r   r_   r   r-   r-   r-   r.   r     s    Gr   c                   @   s$   e Zd Zdd Zdd Zdd ZdS )WSSessionMonitorc                 C   
   t |S r7   )rI   Zsession_monitor_group_name)r4   rH   r-   r-   r.   r9     r   zWSSessionMonitor.group_namec                 C   s   t j|dd}tj|S )NT)Z_session_codevisited)r   r   r   ZexportZget_rows_for_monitor)r4   rH   Zparticipantsr-   r-   r.   get_initial_data  s   z!WSSessionMonitor.get_initial_datac                    s(   | j |d}| t|dI d H  d S )NrG   )rows)r   rd   r   )r4   rH   Zinitial_datar-   r-   r.   rS     s   zWSSessionMonitor.post_connectN)r)   r*   r+   r9   r   rS   r-   r-   r-   r.   r     s    r   c                   @      e Zd Zdd Zdd ZdS )WSRoomAdminc                 C   r   r7   )rI   room_admin_group_name)r4   r   r-   r-   r.   r9     r   zWSRoomAdmin.group_namec                    sR   t | }tdd}|jrttt|j|d< n|j|d< | |I d H  d S )Ninit)r'   Zpresent_labelspresent_count)	r   r   Zhas_participant_labelsrg   fromkeyssortedZpresent_listr   rd   )r4   r   roomrU   r-   r-   r.   rS     s   

zWSRoomAdmin.post_connectNr)   r*   r+   r9   rS   r-   r-   r-   r.   r         r   c                   @   r   )
WSRoomParticipantc                 C   s   t | jd }|dd |S )Nrm   participant_labelr   )rn   r<   
setdefaultr   r-   r-   r.   r6     s   zWSRoomParticipant.clean_kwargsc                 C   r   r7   )rI   r   )r4   r   r   tab_unique_idr-   r-   r.   r9   
  r   zWSRoomParticipant.group_namec                    s`   |t vrd S t | }|| | r| tI d H  d S tjt|d|ddI d H  d S )NZadd_participantr'   Zparticipantr   )r   Zpresence_addZhas_sessionrd   r   rI   r   r   )r4   r   r   r   r   r-   r-   r.   rS     s   
zWSRoomParticipant.post_connectc                    s@   t | }d|d}|| t|}tj||dI d H  d S )NZremove_participantr   r   )r   Zpresence_removerI   r   r   )r4   r   r   r   r   r   Zadmin_groupr-   r-   r.   r\     s   


z WSRoomParticipant.pre_disconnectN)r)   r*   r+   r6   r9   rS   r\   r-   r-   r-   r.   r     s
    r   c                   @   s   e Zd Zdd ZdS )WSBrowserBotsLauncherc                 C   r   r7   )rI   Zbrowser_bots_launcher_group)r4   r   r-   r-   r.   r9   +  r   z WSBrowserBotsLauncher.group_nameN)r)   r*   r+   r9   r-   r-   r-   r.   r   &  s    r   c                   @   r   )WSBrowserBotc                 C   r   )NZbrowser_bot_waitr-   rB   r-   r-   r.   r9   0  rD   zWSBrowserBot.group_namec                    s    t jr| tI d H  d S d S r7   )r	   Z"browser_bots_launcher_session_coderd   r   rB   r-   r-   r.   rS   3  s   zWSBrowserBot.post_connectNr   r-   r-   r-   r.   r   /  r   r   c                   @   s<   e Zd Zdd Zdd Zdd Zdd Zd	d
 Zdd ZdS )WSChatc                 C   s,   t | jd }t|d tt|d dS )Nrm   channelrt   )r   rt   )rn   r<   r   rh   r   r-   r-   r.   r6   9  s   
zWSChat.clean_kwargsc                 C   s   t |S r7   )r   )r4   r   rt   r-   r-   r.   r9   @  s   zWSChat.group_namec                    s6   g d t tj|ddj  } fdd|D S )Nnicknamebodyrt   r   	timestampc                    s   g | ]	}t t |qS r-   )r   zip).0rowfieldsr-   r.   
<listcomp>J      z'WSChat._get_history.<locals>.<listcomp>)rg   r   r   Zorder_byvalues)r4   r   r   r-   r   r.   _get_historyC  s   
zWSChat._get_historyc                    s"   | j |d}| |I d H  d S )Nr   )r   rd   )r4   r   rt   historyr-   r-   r.   rS   L  s   zWSChat.post_connectc           	         s\   |d }t |}|d }t|||d}| j\}tj||gdI d H  | j||||d d S )Nnickname_signedr   r   r   )rt   r   r   r   )r   r   r2   rI   r   _create_message)	r4   rc   r   rt   r   r   r   Zchat_messagerV   r-   r-   r.   r_   T  s   
zWSChat.post_receive_jsonc                 K   s   t jdi | d S r[   )r   Zobjects_creater3   r-   r-   r.   r   g  s   zWSChat._create_messageN)	r)   r*   r+   r6   r9   r   rS   r_   r   r-   r-   r-   r.   r   8  s    	r   c                   @   r   )WSDeleteSessionsc                    s0   t t j|jdd | dI d H  d S )NF)Zsynchronize_sessionok)r   r   rH   in_deleterd   )r4   rc   r-   r-   r.   r_   l  s
   z"WSDeleteSessions.post_receive_jsonc                 K   rA   r7   r-   r3   r-   r-   r.   r9   r  rD   zWSDeleteSessions.group_nameN)r)   r*   r+   r_   r9   r-   r-   r-   r.   r   k  s    r   c                   @   s&   e Zd ZdZdefddZdd ZdS )WSExportDataz
    I load tested this locally with sqlite and:
    - large files up to 22MB (by putting long text in LongStringFields)
    - thousands of participants/rounds, 111000 rows and 20 cols in excel file.
    rc   c           
         s   | d}| d}tj  }z;t -}| dr"|t |r3|r)t	}nt
}||| |}nt| d}| }W d   n1 sGw   Y  W n tyc   |jdd | |I dH   w | d| d	}	|j|	|d
d | |I dH  dS )z
        if an app name is given, export the app.
        otherwise, export all the data (wide).
        don't need time_spent or chat yet, they are quick enough
        r   	is_customZ	for_excelZall_apps_wideNz8Error exporting data. Check the server logs for details.)rQ   _z.csvztext/csv)	file_namer`   Z	mime_type)rN   datetimedatetoday	isoformatioStringIOwriter   r   r   r   getvaluer   r   rd   )
r4   rc   r   r  Ziso_datefpfxnZfile_name_prefixr`   r  r-   r-   r.   r_   ~  s:   






zWSExportData.post_receive_jsonc                 K   rA   r7   r-   r3   r-   r-   r.   r9     rD   zWSExportData.group_nameN)r)   r*   r+   r,   r   r_   r9   r-   r-   r-   r.   r  v  s    'r  rE   c                 C   s   dd t j|   D S )z.it seems parse_qs omits keys with empty valuesc                 S   s   i | ]	\}}||d  qS )r   r-   )r   rp   vr-   r-   r.   
<dictcomp>  r   z%parse_querystring.<locals>.<dictcomp>)urllibparseparse_qsdecodeitems)rm   r-   r-   r.   rn     s   rn   c                   @   s    e Zd ZdZdd Zdd ZdS )LifespanAppz
    temporary shim for https://github.com/django/channels/issues/1216
    needed so that hypercorn doesn't display an error.
    this uses ASGI 2.0 format, not the newer 3.0 single callable
    c                 C   s
   || _ d S r7   r<   )r4   r<   r-   r-   r.   r;     r   zLifespanApp.__init__c                    sf   | j d dkr1	 | I d H }|d dkr|ddiI d H  n|d dkr0|ddiI d H  d S q	d S )Nr   ZlifespanTzlifespan.startupzlifespan.startup.completezlifespan.shutdownzlifespan.shutdown.completer  )r4   ZreceiverJ   messager-   r-   r.   __call__  s   zLifespanApp.__call__N)r)   r*   r+   r,   r;   r  r-   r-   r-   r.   r    s    r  )[r  r	  loggingtimer   urllib.parser  Zstarlette.endpointsr   Zstarlette.websocketsr   Zstarlette.datastructuresr   Zotree.bots.browserr   Zotree.channels.utilsZchannelsutilsrI   Zotree.sessionr   r   r   Zotree.commonr   r	   r
   r   r   Zotree.currencyr   Zotree.databaser   r   r   r   Zotree.exportr   r   r   r   Z
otree.liver   Zotree.modelsr   r   Zotree.models_concreter   r   r   r   Z
otree.roomr   r   r    r!   Zotree.views.adminr"   r#   r$   r%   Zotree.middlewarer&   asyncio	getLoggerr)   rP   r   r   r(   r/   rj   rq   r}   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r   rn   r  r-   r-   r-   r.   <module>   sj    
RT 8X"		33