
    Vh]                       d dl mZ dZd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mZ d d	lmZ d d
lmZ d dlmZ 	 d dlmZ d dlmZ d dlmZ d dlmZ d dl m!Z!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, d dl-m.Z. dZ/ G d d      Z2 G d d      Z3 G d de4      Z5 G d de      Z6y# e0$ rZ1e1Z/Y dZ1[17dZ1[1ww xY w)    )annotationsa3  
author: Victor Martinez (@v1v)  <VictorMartinezRubio@gmail.com>
name: opentelemetry
type: notification
short_description: Create distributed traces with OpenTelemetry
version_added: 3.7.0
description:
  - This callback creates distributed traces for each Ansible task with OpenTelemetry.
  - You can configure the OpenTelemetry exporter and SDK with environment variables.
  - See U(https://opentelemetry-python.readthedocs.io/en/latest/exporter/otlp/otlp.html).
  - See
    U(https://opentelemetry-python.readthedocs.io/en/latest/sdk/environment_variables.html#opentelemetry-sdk-environment-variables).
options:
  hide_task_arguments:
    default: false
    type: bool
    description:
      - Hide the arguments for a task.
    env:
      - name: ANSIBLE_OPENTELEMETRY_HIDE_TASK_ARGUMENTS
    ini:
      - section: callback_opentelemetry
        key: hide_task_arguments
        version_added: 5.3.0
  enable_from_environment:
    type: str
    description:
      - Whether to enable this callback only if the given environment variable exists and it is set to V(true).
      - This is handy when you use Configuration as Code and want to send distributed traces if running in the CI rather when
        running Ansible locally.
      - For such, it evaluates the given O(enable_from_environment) value as environment variable and if set to true this
        plugin will be enabled.
    env:
      - name: ANSIBLE_OPENTELEMETRY_ENABLE_FROM_ENVIRONMENT
    ini:
      - section: callback_opentelemetry
        key: enable_from_environment
        version_added: 5.3.0
    version_added: 3.8.0
  otel_service_name:
    default: ansible
    type: str
    description:
      - The service name resource attribute.
    env:
      - name: OTEL_SERVICE_NAME
    ini:
      - section: callback_opentelemetry
        key: otel_service_name
        version_added: 5.3.0
  traceparent:
    default: None
    type: str
    description:
      - The L(W3C Trace Context header traceparent,https://www.w3.org/TR/trace-context-1/#traceparent-header).
    env:
      - name: TRACEPARENT
  disable_logs:
    default: false
    type: bool
    description:
      - Disable sending logs.
    env:
      - name: ANSIBLE_OPENTELEMETRY_DISABLE_LOGS
    ini:
      - section: callback_opentelemetry
        key: disable_logs
    version_added: 5.8.0
  disable_attributes_in_logs:
    default: false
    type: bool
    description:
      - Disable populating span attributes to the logs.
    env:
      - name: ANSIBLE_OPENTELEMETRY_DISABLE_ATTRIBUTES_IN_LOGS
    ini:
      - section: callback_opentelemetry
        key: disable_attributes_in_logs
    version_added: 7.1.0
  store_spans_in_file:
    type: str
    description:
      - It stores the exported spans in the given file.
    env:
      - name: ANSIBLE_OPENTELEMETRY_STORE_SPANS_IN_FILE
    ini:
      - section: callback_opentelemetry
        key: store_spans_in_file
    version_added: 9.0.0
  otel_exporter_otlp_traces_protocol:
    type: str
    description:
      - E(OTEL_EXPORTER_OTLP_TRACES_PROTOCOL) represents the the transport protocol for spans.
      - See
        U(https://opentelemetry-python.readthedocs.io/en/latest/sdk/environment_variables.html#envvar-OTEL_EXPORTER_OTLP_TRACES_PROTOCOL).
    default: grpc
    choices:
      - grpc
      - http/protobuf
    env:
      - name: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL
    ini:
      - section: callback_opentelemetry
        key: otel_exporter_otlp_traces_protocol
    version_added: 9.0.0
requirements:
  - opentelemetry-api (Python library)
  - opentelemetry-exporter-otlp (Python library)
  - opentelemetry-sdk (Python library)
a  
examples: |-
  Enable the plugin in ansible.cfg:
    [defaults]
    callbacks_enabled = community.general.opentelemetry
    [callback_opentelemetry]
    enable_from_environment = ANSIBLE_OPENTELEMETRY_ENABLED

  Set the environment variable:
    export OTEL_EXPORTER_OTLP_ENDPOINT=<your endpoint (OTLP/HTTP)>
    export OTEL_EXPORTER_OTLP_HEADERS="authorization=Bearer your_otel_token"
    export OTEL_SERVICE_NAME=your_service_name
    export ANSIBLE_OPENTELEMETRY_ENABLED=true
N)time_ns)OrderedDict)basename)AnsibleError)
raise_from)urlparse)CallbackBase)trace)SpanKind)OTLPSpanExporter)SERVICE_NAMEResource)Status
StatusCode)TraceContextTextMapPropagator)TracerProvider)BatchSpanProcessorSimpleSpanProcessor)InMemorySpanExporterc                      e Zd ZdZd Zd Zy)TaskDataz(
    Data about an individual task.
    c                    || _         || _        || _        || _        t	               | _        t               | _        || _        || _	        d | _
        y N)uuidnamepathplayr   	host_datar   startactionargsdump)selfr   r   r   r   r!   r"   s          t/home/dcms/DCMS/lib/python3.12/site-packages/ansible_collections/community/general/plugins/callback/opentelemetry.py__init__zTaskData.__init__   sE    				$Y
		    c                    |j                   | j                  v rH|j                  dk(  r8| j                  |j                      j                   d|j                   |_        ny || j                  |j                   <   y )Nincluded
)r   r   statusresult)r$   hosts     r%   add_hostzTaskData.add_host   s]    99&{{j(!%		!:!A!A B"T[[MR$(tyy!r'   N)__name__
__module____qualname____doc__r&   r.    r'   r%   r   r      s    	)r'   r   c                      e Zd ZdZd Zy)HostDataz(
    Data about an individual host.
    c                Z    || _         || _        || _        || _        t	               | _        y r   )r   r   r+   r,   r   finish)r$   r   r   r+   r,   s        r%   r&   zHostData.__init__   s'    		ir'   N)r/   r0   r1   r2   r&   r3   r'   r%   r5   r5      s     r'   r5   c                      e Zd Zd Zd Zd Zd Zd Zd Zd Z	d Z
ed	        Zed
        Zed        Zed        Zed        Zed        Zed        Zed        Zed        Zed        Zy)OpenTelemetrySourcec                j   d| _         d | _        t        t        j                               | _        t        j                         | _        	 t        j                  t        j                               | _
        t        j                         | _        || _        y # t        $ r}d | _
        Y d }~6d }~ww xY w)N )ansible_playbookansible_versionstrr   uuid4sessionsocketgethostnamer-   gethostbyname
ip_address	Exceptiongetpassgetuseruser_display)r$   displayes      r%   r&   zOpenTelemetrySource.__init__   s     "#4::<(&&(		#$2263E3E3GHDO OO%		  	#"DOO	#s   ,B 	B2!B--B2c                T    t               }||d<   t               j                  |      S )Ntraceparent)carrier)dictr   extract)r$   rM   rN   s      r%   traceparent_contextz'OpenTelemetrySource.traceparent_context   s*    &!,,.66w6GGr'   c                    |j                   }||v ry|j                         j                         }|j                         }|j                  }d}	|j
                  s|s|j                  }	t        ||||||	      ||<   y)z2 record the start of a task for one or more hosts N)_uuidget_namestripget_pathr!   no_logr"   r   )
r$   
tasks_datahide_task_arguments	play_nametaskr   r   r   r!   r"   s
             r%   
start_taskzOpenTelemetrySource.start_task   sq     zz:}}$$&}}{{#699D#D$iN
4r'   c                   |j                   j                  }t        |d      r9|j                  -|j                  j                  }|j                  j                  }nd}d}||   }| j
                  Mt        |d      rA|j                  d   j                  d      r#|j                  d   j                  d      | _        ||_        |j                  t        ||||             y)z0 record the results of a task for a single host _hostNinclude_task_fieldsr"   _ansible_version)_taskrS   hasattrr^   r   r=   r`   getr#   r.   r5   )	r$   rX   r+   r,   r#   	task_uuid	host_uuid	host_namer[   s	            r%   finish_taskzOpenTelemetrySource.finish_task   s     LL&&	67#(@**I))I!I!I)$'GFN,KPVPcPcdjPkPoPo  qC  QD#)#6#6v#>#B#BCU#VD 	hy)VVDEr'   c
                   g }
d}|j                         D ]$  \  }}||j                  }|
j                  |       & t        j                  t        t        j                  t        |i                   d}|	rt               }t        |      }n%|dk(  rt               }n
t               }t        |      }t        j                         j                  |       t        j                   t"              }|j%                  || j'                  |      |t(        j*                        5 }|j-                  |       | j.                  |j1                  d| j.                         |j1                  d| j2                         |j1                  d| j4                         | j6                  |j1                  d| j6                         |j1                  d	| j8                         |
D ]j  }|j:                  j                         D ]K  \  }}|j%                  |j<                  |j                  d
      5 }| j?                  |||||       ddd       M l 	 ddd       |S # 1 sw Y   dxY w# 1 sw Y   |S xY w)zF generate distributed traces from the collected TaskData and HostData N)resourcegrpc)context
start_timekindzansible.versionzansible.sessionzansible.host.namezansible.host.ipzansible.host.userF)rm   end_on_exit) itemsr    appendr   set_tracer_providerr   r   creater   r   r   GRPCOTLPSpanExporterHTTPOTLPSpanExporterr   get_tracer_provideradd_span_processor
get_tracerr/   start_as_current_spanrQ   r   SERVER
set_statusr=   set_attributer@   r-   rD   rH   r   r   update_span_data)r$   otel_service_namer<   rX   r+   rM   disable_logsdisable_attributes_in_logs"otel_exporter_otlp_traces_protocolstore_spans_in_filetasksparent_start_timere   r[   otel_exporter	processortracerparentrf   r   spans                        r%   generate_distributed_tracesz/OpenTelemetrySource.generate_distributed_traces  sD     )//1 	OIt ($(JJ!LL	
 	!!!,8I)JK	
 02M+M:I1V; 4 6 4 6*=9I!!#66yA!!(+))*:DD\D\]hDi5FX__ * ^ 	oagf%##/$$%68L8LM  !2DLLA  !4dii@*$$%6H  !4dii@ o,0NN,@,@,B o(Iy55diiDJJdi5j onr--dIt\Smno ooo	o  o o	o  s%   !DI$I	:IIII'c                    d|j                    d|j                   d|j                    }d}i }d}	t        t        j                        }
|j
                  dk7  rd}d	|j                  j                  v rv|j
                  d
k(  r j                  |j                  j                  d	   |j                        } j                  |j                  j                  d	   |j                        }nY|j                  j                  }|j                  dd      }	|j
                  d
k(  r" j                  |      } j                  |      }|j
                  d
k(  r6t        t        j                  |      }
|j                  t!        |             n^|j
                  dk(  r&d|v r|d   nd}t        t        j"                        }
n)|j
                  dk(  rt        t        j"                        }
|j%                  |
       |j                  |||	|j                   |j
                  d}t'        |j(                  t*              rpd|j                  vrbt-         fd|j(                  j/                         D              }t-         fd|j(                  j1                         D              }||d<   ||d<    j3                  ||        j5                  ||       |s!|j7                  |j8                  |ri n|       |j;                  |j<                         y)z6 update the span with the given TaskData and HostData [z] z: successr   status_coder)   Nresultsfailedrc)r   descriptionskippedskip_reasonignored)zansible.task.modulezansible.task.messagezansible.task.namezansible.task.resultzansible.task.host.namezansible.task.host.statusgather_factsc              3  @   K   | ]  }j                  |        y wr    transform_ansible_unicode_to_str.0kr$   s     r%   	<genexpr>z7OpenTelemetrySource.update_span_data.<locals>.<genexpr>t  s     bq$??Bb   c              3  @   K   | ]  }j                  |        y wr   r   r   s     r%   r   z7OpenTelemetrySource.update_span_data.<locals>.<genexpr>u  s     e4@@Cer   zansible.task.args.namezansible.task.args.value)
attributes)end_time)r   r   r   r   OKr+   r,   _resultget_error_message_from_resultsr!   !enrich_error_message_from_resultsrd   get_error_messageenrich_error_messageERRORrecord_exceptionBaseExceptionUNSETr{   
isinstancer"   rO   tuplekeysvaluesset_span_attributes*add_attributes_for_service_map_if_possible	add_eventr#   endr7   )r$   	task_datar   r   r   r   r   messageresr   r+   enriched_error_messager   namesr   s   `              r%   r}   z$OpenTelemetrySource.update_span_dataG  s    9>>""Y^^$4By~~6FGJMM2z)%)"I,,444##x/"AA)BRBRBZBZ[dBegpgwgwxG-1-S-ST]TdTdTlTlmvTw  zC  zJ  zJ  .K*&&..WWT1%##x/"44S9G-1-F-Fs-K*8+J,<,<'R%%m4J&KL!!Y.0=0D#m,)J,<,<=!!Y.J,<,<= $-#3#3$+!%#%&/nn(1(8(8

 innd+iFVFV0VbINNL_L_LabbEeY^^MbMbMdeeF5:J026<J13  z2 	77iHNN9>><Vb\fNg)**+r'   c                    |(| j                   | j                   j                  d       y||j                  |       yy)zB update the span attributes with the given attributes if not None Nz=span object is None. Please double check if that is expected.)rI   warningset_attributes)r$   r   r   s      r%   r   z'OpenTelemetrySource.set_span_attributes  s>     <DMM5MM!!"ab%##J/ &r'   c                    | j                  |j                        }|r!|j                  d|j                                yy)zWUpdate the span attributes with the service that the task interacted with, if possible.zhttp.urlN) parse_and_redact_url_if_possibler"   r|   geturl)r$   r   r   redacted_urls       r%   r   z>OpenTelemetrySource.add_attributes_for_service_map_if_possible  s9     <<Y^^Lz<+>+>+@A r'   c                    	 t        t        j                  |             }t        j	                  |      rt        j                  |      S y# t        $ r Y yw xY w)z&Parse and redact the url, if possible.N)r	   r9   url_from_args
ValueErroris_valid_urlredact_user_password)r"   
parsed_urls     r%   r   z4OpenTelemetrySource.parse_and_redact_url_if_possible  sU    	!"5"C"CD"IJJ ++J7&;;JGG  		s   A 	AAc                d    d}|D ])  }| | j                  |      s| j                  |      c S  y)N)
urlapi_urlbaseurlrepo
server_urlchart_repo_urlregistry_urlendpointuriupdates_urlr;   rd   )r"   url_argsargs      r%   r   z!OpenTelemetrySource.url_from_args  s<     K 	%CDHHSMxx}$	% r'   c                V    | j                   r| j                  | j                        S | S )N)netloc)password_replacehostnamer   s    r%   r   z(OpenTelemetrySource.redact_user_password  s"    47LLs||3<<|0IcIr'   c                x    t        | j                  | j                  | j                  g      rd| j                  vS y)Nz{{F)allschemer   r   r   s    r%   r   z OpenTelemetrySource.is_valid_url  s/    

CJJ56s||++r'   c                    t        t        |             }t        j                  |      r#t        j	                  |      j                         S t        |       S r   )r	   r>   r9   r   r   r   )valuer   s     r%   r   z4OpenTelemetrySource.transform_ansible_unicode_to_str  sB    c%j)
++J7&;;JGNNPP5zr'   c                x    | j                  d      t        j                  | d         S | j                  dd      S )N	exceptionmsgr   )rd   r9   
_last_line)r,   s    r%   r   z%OpenTelemetrySource.get_error_message  s8    ::k".&11&2EFFzz%**r'   c                    | D ]C  }|j                  dd      s| d|j                  dd       dt        j                  |       c S  y )Nr   F(itemnone) - )rd   r9   r   )r   r!   r,   s      r%   r   z2OpenTelemetrySource.get_error_message_from_results  sR     	sFzz(E* 6::ff#=">dCVChChioCpBqrr	sr'   c                J    | j                         j                  d      }|d   S )Nr*   )rU   split)textliness     r%   r   zOpenTelemetrySource._last_line  s"    

""4(Ryr'   c                    | j                  dd      }| j                  d      }| j                  d      }d| d| d| dS )	Nr   r   r   stderrz
message: "z"
exception: "z"
stderr: ""r   )r,   r   r   r   s       r%   r   z(OpenTelemetrySource.enrich_error_message  sL    **UH-JJ{+	H%WI%6ykPVxWYZZr'   c                    d}| D ]E  }|j                  dd      s| d|j                  dd       dt        j                  |       d| }G |S )	Nr;   r   Fr   r   r   r   r*   )rd   r9   r   )r   r!   r   r,   s       r%   r   z5OpenTelemetrySource.enrich_error_message_from_results  sq     	DFzz(E*#HAfjj&@%AFYFnFnouFvEwwy  {B  zC  D	D r'   N)r/   r0   r1   r&   rQ   r\   rh   r   r}   r   r   staticmethodr   r   r   r   r   r   r   r   r   r   r3   r'   r%   r9   r9      s     H
O$F(8t;,z0B 
 
   J J  
   + +
 s s
   [ [  r'   r9   c                       e Zd ZdZdZdZdZdZd fd	Zd fd	Z	d Z
d	 Zd
 Zd Zd Zd Zd ZddZd Zd Zd Zd Zd Z xZS )CallbackModulez3
    This callback creates distributed traces.
    g       @notificationzcommunity.general.opentelemetryTc                h   t         t        |   |       d | _        d | _        d | _        d | _        d | _        d | _        d | _	        d| _
        d| _        d| _        d| _        d | _        t        rt!        t#        d      t               t%               | _	        t'        | j(                        | _        y )N)rJ   r   FzrThe `opentelemetry-api`, `opentelemetry-exporter-otlp` or `opentelemetry-sdk` must be installed to use this plugin)superr   r&   rY   r   r   r~   r<   rZ   rX   errorsdisabledrM   r   r   OTEL_LIBRARY_IMPORT_ERRORr   r   r   r9   rI   opentelemetry)r$   rJ   	__class__s     r%   r&   zCallbackModule.__init__  s    nd,W,=#' *.' !% $ #( 26/$  R  S)+ &-0Gr'   c                `   t         t        |   |||       | j                  d      }|Wt        j
                  j                  |d      j                         dk7  r&d| _        | j                  j                  d| d       | j                  d      | _        | j                  d	      | _        | j                  d
      | _        | j                  d      | _        | j                  d      | _        | j                  sd| _        | j                  d      | _        | j                  d      | _        y )N)	task_keysvar_optionsdirectenable_from_environmentfalsetrueTz6The `enable_from_environment` option has been set and z? is not enabled. Disabling the `opentelemetry` callback plugin.rY   r   r   r   r~   ansiblerM   r   )r   r   set_options
get_optionosenvironrd   lowerr   rI   r   rY   r   r   r   r~   rM   r   )r$   r   r   r   environment_variabler   s        r%   r  zCallbackModule.set_options  s   nd/)<G7= 	0 	?  $/HI+

?SU\0]0c0c0eio0o DMMM!!HI]H^  _^  _ $(??3H#I *.//:V*W' OON;#'??3H#I !%1D!E%%%.D"  ??=926//Bf2g/r'   c                    | j                   ryt        |j                        }d|v r|j                  dv r|j	                  d       d|v r|j                  dv r|j	                  d       | j                  |      S )z1 dump the results if disable_logs is not enabled r;   json)zansible.builtin.urizansible.legacy.urir   content)zansible.builtin.slurpzansible.legacy.slurpslurp)r   rO   r   r!   pop_dump_results)r$   r[   r,   saves       r%   dump_resultszCallbackModule.dump_results  sl    FNN#T>dkk-aaHHV0j!jHHY!!$''r'   c                8    t        |j                        | _        y r   )r   
_file_namer<   )r$   playbooks     r%   v2_playbook_on_startz#CallbackModule.v2_playbook_on_start*  s     ()<)< =r'   c                .    |j                         | _        y r   )rT   rZ   )r$   r   s     r%   v2_playbook_on_play_startz(CallbackModule.v2_playbook_on_play_start-  s    r'   c                |    | j                   j                  | j                  | j                  | j                  |       y r   r   r\   rX   rY   rZ   r$   r[   s     r%   v2_runner_on_no_hostsz$CallbackModule.v2_runner_on_no_hosts0  0    %%OO$$NN		
r'   c                |    | j                   j                  | j                  | j                  | j                  |       y r   r  )r$   r[   is_conditionals      r%   v2_playbook_on_task_startz(CallbackModule.v2_playbook_on_task_start8  r  r'   c                |    | j                   j                  | j                  | j                  | j                  |       y r   r  r  s     r%   !v2_playbook_on_cleanup_task_startz0CallbackModule.v2_playbook_on_cleanup_task_start@  r  r'   c                |    | j                   j                  | j                  | j                  | j                  |       y r   r  r  s     r%   !v2_playbook_on_handler_task_startz0CallbackModule.v2_playbook_on_handler_task_startH  r  r'   c           	         |rd}nd}| xj                   dz  c_         | j                  j                  | j                  ||| j	                  | j                  |j
                  j                     |             y )Nr   r      )r   r   rh   rX   r  rb   rS   )r$   r,   ignore_errorsr+   s       r%   v2_runner_on_failedz"CallbackModule.v2_runner_on_failedP  sa    FFKK1K&&OOdoofll.@.@A6J		
r'   c           	         | j                   j                  | j                  d|| j                  | j                  |j                  j
                     |             y )Nokr   rh   rX   r  rb   rS   r$   r,   s     r%   v2_runner_on_okzCallbackModule.v2_runner_on_ok^  sF    &&OOdoofll.@.@A6J		
r'   c           	         | j                   j                  | j                  d|| j                  | j                  |j                  j
                     |             y )Nr   r+  r,  s     r%   v2_runner_on_skippedz#CallbackModule.v2_runner_on_skippedf  sF    &&OOdoofll.@.@A6J		
r'   c                T    | j                   j                  | j                  d|d       y )Nr)   r;   )r   rh   rX   )r$   included_files     r%   v2_playbook_on_includez%CallbackModule.v2_playbook_on_includen  s&    &&OO		
r'   c                   | j                   dk(  rt        t        j                        }nt        t        j                        }| j
                  j                  | j                  | j                  | j                  || j                  | j                  | j                  | j                  | j                  	      }| j                  rz|j                         D cg c]%  }t!        j"                  |j%                               ' }}t'        | j                  dd      5 }t!        j(                  d|i|d       d d d        y y c c}w # 1 sw Y   y xY w)	Nr   r   wzutf-8)encodingspans   )indent)r   r   r   r   r   r   r   r~   r<   rX   rM   r   r   r   r   get_finished_spansr  loadsto_jsonopenr#   )r$   statsr+   r   r   r6  outputs          r%   v2_playbook_on_statsz#CallbackModule.v2_playbook_on_statsv  s   ;;!
6F
(8(89F**FF""!!OO++33$$

 ##<I<\<\<^_DTZZ/_E_d..gF >&		7E*F1=> > $_> >s   *D?EEc                .    | xj                   dz  c_         y )Nr&  )r   )r$   r,   kwargss      r%   v2_runner_on_async_failedz(CallbackModule.v2_runner_on_async_failed  s    qr'   r   )NNN)F)r/   r0   r1   r2   CALLBACK_VERSIONCALLBACK_TYPECALLBACK_NAMECALLBACK_NEEDS_ENABLEDr&   r  r  r  r  r  r   r"  r$  r(  r-  r/  r2  r?  rB  __classcell__)r   s   @r%   r   r     sm     "M5M!H0h<(>)







>,r'   r   )7
__future__r   DOCUMENTATIONEXAMPLESrF   r  r  rA   r   timer   collectionsr   os.pathr   ansible.errorsr   ansible.module_utils.sixr   +ansible.module_utils.six.moves.urllib.parser	   ansible.plugins.callbackr
   r   r   opentelemetry.tracer   5opentelemetry.exporter.otlp.proto.grpc.trace_exporterr   rt   5opentelemetry.exporter.otlp.proto.http.trace_exporterru   opentelemetry.sdk.resourcesr   r   opentelemetry.trace.statusr   r   ,opentelemetry.trace.propagation.tracecontextr   opentelemetry.sdk.tracer   opentelemetry.sdk.trace.exportr   r   6opentelemetry.sdk.trace.export.in_memory_span_exporterr   r   ImportErrorimp_excr   r5   objectr9   r   r3   r'   r%   <module>r^     s    #m`   	    #  ' / @ 1%#,nnB=Z6 !%) )6
  
 E& EPq\ qm	  ( '(s   
AB9 9C
>CC
