
    hi              	          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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 dZdZda ej(                  d      Z ej(                  d      Z ej                  dd	d	      Zd
 ZddZddZddZdddej8                  dddddf	dZddZddZddZ  G d de!      Z" G d de!      Z#y)a  
Descriptor archives are available from `CollecTor
<https://metrics.torproject.org/collector.html>`_. If you need Tor's topology
at a prior point in time this is the place to go!

With CollecTor you can either read descriptors directly...

.. literalinclude:: /_static/example/collector_reading.py
   :language: python

... or download the descriptors to disk and read them later.

.. literalinclude:: /_static/example/collector_caching.py
   :language: python

::

  get_instance - Provides a singleton CollecTor used for...
    |- get_server_descriptors - published server descriptors
    |- get_extrainfo_descriptors - published extrainfo descriptors
    |- get_microdescriptors - published microdescriptors
    |- get_consensus - published router status entries
    |
    |- get_key_certificates - authority key certificates
    |- get_bandwidth_files - bandwidth authority heuristics
    +- get_exit_lists - TorDNSEL exit list

  File - Individual file residing within CollecTor
    |- read - provides descriptors from this file
    +- download - download this file to disk

  CollecTor - Downloader for descriptors from CollecTor
    |- get_server_descriptors - published server descriptors
    |- get_extrainfo_descriptors - published extrainfo descriptors
    |- get_microdescriptors - published microdescriptors
    |- get_consensus - published router status entries
    |
    |- get_key_certificates - authority key certificates
    |- get_bandwidth_files - bandwidth authority heuristics
    |- get_exit_lists - TorDNSEL exit list
    |
    |- index - metadata for content available from CollecTor
    +- files - files available from CollecTor

.. versionadded:: 1.8.0
    N)CompressionDocumentHandlerz!https://collector.torproject.org/  z-(\d{4})-(\d{2})\.z%(\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2})i'     c                  .    t         
t               a t         S )z
  Provides the singleton :class:`~stem.descriptor.collector.CollecTor`
  used for this module's shorthand functions.

  :returns: singleton :class:`~stem.descriptor.collector.CollecTor` instance
  )SINGLETON_COLLECTOR	CollecTor     c/var/www/betterdocs.net/sherlock_api/venv/lib/python3.12/site-packages/stem/descriptor/collector.pyget_instancer   P   s      #+	r   F   c              #   Z   K   t               j                  | |||||      D ]  }|  yw)zv
  Shorthand for
  :func:`~stem.descriptor.collector.CollecTor.get_server_descriptors`
  on our singleton instance.
  N)r   get_server_descriptorsstartendcache_tobridgetimeoutretriesdescs          r   r   r   `   s5      n33E3&RY[bc d
J   )+c              #   Z   K   t               j                  | |||||      D ]  }|  yw)zy
  Shorthand for
  :func:`~stem.descriptor.collector.CollecTor.get_extrainfo_descriptors`
  on our singleton instance.
  N)r   get_extrainfo_descriptorsr   s          r   r   r   k   s5      n66uc8VU\^ef d
Jr   c              #   X   K   t               j                  | ||||      D ]  }|  yw)zt
  Shorthand for
  :func:`~stem.descriptor.collector.CollecTor.get_microdescriptors`
  on our singleton instance.
  N)r   get_microdescriptorsr   r   r   r   r   r   s         r   r   r   v   2      n11%hQXY d
J   (*c	              #   `   K   t               j                  | ||||||||	      D ]  }	|	  yw)zm
  Shorthand for
  :func:`~stem.descriptor.collector.CollecTor.get_consensus`
  on our singleton instance.
  N)r   get_consensus)
r   r   r   document_handlerversionmicrodescriptorr   r   r   r   s
             r   r"   r"      sF      n**5#xAQSZ\kmsu|  F  G d
Js   ,.c              #   X   K   t               j                  | ||||      D ]  }|  yw)zt
  Shorthand for
  :func:`~stem.descriptor.collector.CollecTor.get_key_certificates`
  on our singleton instance.
  N)r   get_key_certificatesr   s         r   r'   r'      r   r    c              #   X   K   t               j                  | ||||      D ]  }|  yw)zs
  Shorthand for
  :func:`~stem.descriptor.collector.CollecTor.get_bandwidth_files`
  on our singleton instance.
  N)r   get_bandwidth_filesr   s         r   r)   r)      s2      n00XwPWX d
Jr    c              #   X   K   t               j                  | ||||      D ]  }|  yw)zn
  Shorthand for
  :func:`~stem.descriptor.collector.CollecTor.get_exit_lists`
  on our singleton instance.
  N)r   get_exit_listsr   s         r   r+   r+      s1      n++E3'7S d
Jr    c                   h    e Zd ZdZd Zddddej                  ddfdZd	dZe	d        Z
e	d        Zy)
FileaF  
  File within CollecTor.

  :var str path: file path within collector
  :var tuple types: descriptor types contained within this file
  :var stem.descriptor.Compression compression: file compression, **None** if
    this cannot be determined
  :var int size: size of the file
  :var str sha256: file's sha256 checksum

  :var datetime start: first publication within the file, **None** if this
    cannot be determined
  :var datetime end: last publication within the file, **None** if this cannot
    be determined
  :var datetime last_modified: when the file was last modified
  c                    || _         |rt        |      nd| _        t        j	                  |      | _        || _        || _        t        j                  j                  |d      | _
        d | _        |rM|rKt        j                  j                  |d      | _        t        j                  j                  |d      | _        y t        j                  |      \  | _        | _        y )Nr
   z%Y-%m-%d %H:%M)pathtupletypesr-   _guess_compressioncompressionsizesha256datetimestrptimelast_modified_downloaded_tor   r   _guess_time_range)selfr/   r1   r4   r5   first_publishedlast_publishedr8   s           r   __init__zFile.__init__   s    DI!&uBDJ..t4DDIDK!**33MCSTDD
 >$$--o?OPdj""++N<LMdh!33D9dj$(r   Nr   c           	   #     K   |t        | j                  D cg c]  }|j                  d      d    c}      }	| j                  st        d      t	        |	      dkD  r't        ddj                  | j                        z        | j                  d   }|| j                  rSt        j                  j                  | j                        r*t        j                  j                  | j                        }nJt        j                         }
| j                  |
||||||      D ]  }|  t        j                  |
       y| j!                  |d||      }t"        j$                  j'                  ||	      D ]Q  }|*|j)                  |j+                         j,                        s/t/        |d
d      }|r|r||k  rF|r||kD  rN| S yc c}w w)a  
    Provides descriptors from this archive. Descriptors are downloaded or read
    from disk as follows...

    * If this file has already been downloaded through
      :func:`~stem.descriptor.collector.CollecTor.download' these descriptors
      are read from disk.

    * If a **directory** argument is provided and the file is already present
      these descriptors are read from disk.

    * If a **directory** argument is provided and the file is not present the
      file is downloaded this location then read.

    * If the file has neither been downloaded and no **directory** argument
      is provided then the file is downloaded to a temporary directory that's
      deleted after it is read.

    :param str directory: destination to download into
    :param str descriptor_type: `descriptor type
      <https://metrics.torproject.org/collector.html#data-formats>`_, this is
      guessed if not provided
    :param datetime.datetime start: publication time to begin with
    :param datetime.datetime end: publication time to end with
    :param stem.descriptor.__init__.DocumentHandler document_handler: method in
      which to parse a :class:`~stem.descriptor.networkstatus.NetworkStatusDocument`
    :param int timeout: timeout when connection becomes idle, no timeout
      applied if **None**
    :param int retries: maximum attempts to impose

    :returns: iterator for :class:`~stem.descriptor.__init__.Descriptor`
      instances in the file

    :raises:
      * **ValueError** if unable to determine the descirptor type
      * **TypeError** if we cannot parse this descriptor type
      * :class:`~stem.DownloadFailed` if the download fails
    N r   z/Unable to determine this file's descriptor typer   z;Unable to disambiguate file's descriptor type from among %sz, T)r#   	published)setr1   split
ValueErrorlenjoinr9   osr/   existsdirnametempfilemkdtempreadshutilrmtreedownloadstem
descriptor
parse_file
startswithtype_annotationnamegetattr)r;   	directorydescriptor_typer   r   r#   r   r   t
base_typestmp_directoryr   r/   rA   s                 r   rL   z	File.read   s    P 
 <AQ<=jZZJKKz?QVY]YbYbcgcmcmYnnoo**Q-			0C0C!DGGOOD$7$78	
 !((*IIm_eSJZ\celm 	D*	 	m$==D'7;D
 **4DT*U 		 O$>$>t?S?S?U?Z?Z$[ D+t4	y5(y3
= =s   GG
E0G$+Gc           	      F   | j                   j                  d      d   }| j                  t        j                  k7  r|r|j                  dd      d   }t        j                   j                  |      }t        j                   j                  ||      }t        j                   j                  |      st        j                  |       t        j                   j                  |      rt        |      5 }t        j                  t        j                  | j                               }	t#        j                   |j%                               j'                         }
|	|
k(  r|cddd       S |st)        |d|	d|
d	      	 ddd       t*        j,                  j.                  j1                  t2        | j                   z   ||      }|r| j                  j5                  |      }t        |d
      5 }|j7                  |       ddd       || _        |S # 1 sw Y   xY w# 1 sw Y   xY w)a  
    Downloads this file to the given location. If a file already exists this is
    a no-op.

    :param str directory: destination to download into
    :param bool decompress: decompress written file
    :param int timeout: timeout when connection becomes idle, no timeout
      applied if **None**
    :param int retries: maximum attempts to impose
    :param bool overwrite: if this file exists but mismatches CollecTor's
      checksum then overwrites if **True**, otherwise rases an exception

    :returns: **str** with the path we downloaded to

    :raises:
      * :class:`~stem.DownloadFailed` if the download fails
      * **IOError** if a mismatching file exists and **overwrite** is **False**
    /.r   r   Nz? already exists but mismatches CollecTor's checksum (expected: z
, actual: )wb)r/   rC   r3   r   	PLAINTEXTrsplitrG   
expanduserrF   rH   makedirsopenbinasciihexlifybase64	b64decoder5   hashlibrL   	hexdigestIOErrorrP   util
connectionrO   COLLECTOR_URL
decompresswriter9   )r;   rW   rq   r   r   	overwritefilenamer/   
prior_fileexpected_hashactual_hashresponseoutput_files                r   rO   zFile.download+  s   ( yys#B'H;000Za(+h""9-I77<<	8,D77>>)$kk) 
ww~~d: Q (()9)9$++)FGnnZ__%67AACK'Q Q nr  uB  DO  P  Q  Q Q yy##,,]TYY-FQXYH!!,,X6h	dD	 "[!" DK%Q Q" "s   0A*H$H(HHH c                     t         j                  t         j                  t         j                  fD ]!  }| j	                  |j
                        s|c S  t         j                  S )z>
    Determine file comprssion from CollecTor's filename.
    )r   LZMABZ2GZIPendswith	extensionrb   )r/   r3   s     r   r2   zFile._guess_compressionb  sP     $((+//;;K;KL 	{,,	-    r   c                    t         j                  |       }|rut        t        |j	                               \  }}t        j
                  ||d      }|dk  r|t        j
                  ||dz   d      fS |t        j
                  |dz   dd      fS t        j                  |       }|rJt
        j
                  j                  |j                  d      d      }||t        j                  d      z   fS y)z
    Attemt to determine the (start, end) time range from CollecTor's filename.
    This provides (None, None) if this cannot be determined.
    r      z%Y-%m-%d-%H-%M-%Sr   )seconds)NN)
	YEAR_DATEsearchmapintgroupsr6   SEC_DATEr7   group	timedelta)r/   
year_matchyearmonthr   	sec_matchs         r   r:   zFile._guess_time_rangen  s     !!$'JZ..01kdEeQ/e	x((uqy!<==x((1a899%I
 (();=PQeUX//$??@@r   )TNr   F)__name__
__module____qualname____doc__r>   r   ENTRIESrL   rO   staticmethodr2   r:   r
   r   r   r-   r-      sc    ":$ "T4thwhh  LP  \] Xt5n 	! 	!  r   r-   c            	           e Zd ZdZddZddZddZddZdddej                  dddddf	d	Z
dd
ZddZddZddZddZed        Zy)r	   aS  
  Downloader for descriptors from CollecTor. The contents of CollecTor are
  provided in `an index <https://collector.torproject.org/index/index.json>`_
  that's fetched as required.

  :var int retries: number of times to attempt the request if downloading it
    fails
  :var float timeout: duration before we'll time out our request
  Nc                 J    || _         || _        d | _        d | _        d| _        y )Nr   )r   r   _cached_index_cached_files_cached_index_at)r;   r   r   s      r   r>   zCollecTor.__init__  s(    DLDLDDDr   Fr   c           	   #      K   |sdnd}| j                  |||      D ]"  }|j                  ||||||      D ]  }	|	  $ yw)aL  
    Provides server descriptors published during the given time range, sorted
    oldest to newest.

    :param datetime.datetime start: publication time to begin with
    :param datetime.datetime end: publication time to end with
    :param str cache_to: directory to cache archives into, if an archive is
      available here it is not downloaded
    :param bool bridge: standard descriptors if **False**, bridge if **True**
    :param int timeout: timeout for downloading each individual archive when
      the connection becomes idle, no timeout applied if **None**
    :param int retries: maximum attempts to impose on a per-archive basis

    :returns: **iterator** of
      :class:`~stem.descriptor.server_descriptor.ServerDescriptor` for the
      given time range

    :raises: :class:`~stem.DownloadFailed` if the download fails
    zserver-descriptorzbridge-server-descriptorr   r   NfilesrL   
r;   r   r   r   r   r   r   	desc_typefr   s
             r   r   z CollecTor.get_server_descriptors  sY     * ,2#7QIZZ	5#. &&9eSGW^&_ $
   A Ac           	   #      K   |sdnd}| j                  |||      D ]"  }|j                  ||||||      D ]  }	|	  $ yw)aZ  
    Provides extrainfo descriptors published during the given time range,
    sorted oldest to newest.

    :param datetime.datetime start: publication time to begin with
    :param datetime.datetime end: publication time to end with
    :param str cache_to: directory to cache archives into, if an archive is
      available here it is not downloaded
    :param bool bridge: standard descriptors if **False**, bridge if **True**
    :param int timeout: timeout for downloading each individual archive when
      the connection becomes idle, no timeout applied if **None**
    :param int retries: maximum attempts to impose on a per-archive basis

    :returns: **iterator** of
      :class:`~stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor`
      for the given time range

    :raises: :class:`~stem.DownloadFailed` if the download fails
    z
extra-infozbridge-extra-infor   Nr   r   s
             r   r   z#CollecTor.get_extrainfo_descriptors  sX     * %+0CIZZ	5#. &&9eSGW^&_ $
r   c           	   #   |   K   | j                  d||      D ]"  }|j                  |d||||      D ]  }|  $ yw)a  
    Provides microdescriptors estimated to be published during the given time
    range, sorted oldest to newest. Unlike server/extrainfo descriptors,
    microdescriptors change very infrequently...

    ::

      "Microdescriptors are expected to be relatively static and only change
      about once per week." -dir-spec section 3.3

    CollecTor archives only contain microdescriptors that *change*, so hourly
    tarballs often contain very few. Microdescriptors also do not contain
    their publication timestamp, so this is estimated.

    :param datetime.datetime start: publication time to begin with
    :param datetime.datetime end: publication time to end with
    :param str cache_to: directory to cache archives into, if an archive is
      available here it is not downloaded
    :param int timeout: timeout for downloading each individual archive when
      the connection becomes idle, no timeout applied if **None**
    :param int retries: maximum attempts to impose on a per-archive basis

    :returns: **iterator** of
      :class:`~stem.descriptor.microdescriptor.Microdescriptor
      for the given time range

    :raises: :class:`~stem.DownloadFailed` if the download fails
    r%   r   Nr   r;   r   r   r   r   r   r   r   s           r   r   zCollecTor.get_microdescriptors  sP     < ZZ)5#6 &&#4eSG_f&g $
   :<c
           
   #     K   |dk(  r|s|sd}
n@|dk(  r|r|sd}
n4|dk(  r|s|sd}
n(|rd}
n#|r|dk7  rt        d|z        t        d|z        | j                  |
||      D ]#  }|j                  ||
|||||		      D ]  }|  % y
w)a  
    Provides consensus router status entries published during the given time
    range, sorted oldest to newest.

    :param datetime.datetime start: publication time to begin with
    :param datetime.datetime end: publication time to end with
    :param str cache_to: directory to cache archives into, if an archive is
      available here it is not downloaded
    :param stem.descriptor.__init__.DocumentHandler document_handler: method in
      which to parse a :class:`~stem.descriptor.networkstatus.NetworkStatusDocument`
    :param int version: consensus variant to retrieve (versions 2 or 3)
    :param bool microdescriptor: provides the microdescriptor consensus if
      **True**, standard consensus otherwise
    :param bool bridge: standard descriptors if **False**, bridge if **True**
    :param int timeout: timeout for downloading each individual archive when
      the connection becomes idle, no timeout applied if **None**
    :param int retries: maximum attempts to impose on a per-archive basis

    :returns: **iterator** of
      :class:`~stem.descriptor.router_status_entry.RouterStatusEntry`
      for the given time range

    :raises: :class:`~stem.DownloadFailed` if the download fails
    r   znetwork-status-consensus-3z$network-status-microdesc-consensus-3   znetwork-status-2zbridge-network-statusz7Only v3 microdescriptors are available (not version %s)zCOnly v2 and v3 router status entries are available (not version %s)r   N)rD   r   rL   )r;   r   r   r   r#   r$   r%   r   r   r   r   r   r   s                r   r"   zCollecTor.get_consensus  s     4 !|OF.i	A/&8i	Aof$i	)i	W\RU\\]]^ahhiiZZ	5#. &&9eS:JV]ip&q $
s   BB	c           	   #   |   K   | j                  d||      D ]"  }|j                  |d||||      D ]  }|  $ yw)a  
    Directory authority key certificates for the given time range,
    sorted oldest to newest.

    :param datetime.datetime start: publication time to begin with
    :param datetime.datetime end: publication time to end with
    :param str cache_to: directory to cache archives into, if an archive is
      available here it is not downloaded
    :param int timeout: timeout for downloading each individual archive when
      the connection becomes idle, no timeout applied if **None**
    :param int retries: maximum attempts to impose on a per-archive basis

    :returns: **iterator** of
      :class:`~stem.descriptor.networkstatus.KeyCertificate
      for the given time range

    :raises: :class:`~stem.DownloadFailed` if the download fails
    zdir-key-certificate-3r   Nr   r   s           r   r'   zCollecTor.get_key_certificates$  sQ     ( ZZ/< &&#:E3RYel&m $
r   c           	   #   |   K   | j                  d||      D ]"  }|j                  |d||||      D ]  }|  $ yw)a  
    Bandwidth authority heuristics for the given time range, sorted oldest to
    newest.

    :param datetime.datetime start: publication time to begin with
    :param datetime.datetime end: publication time to end with
    :param str cache_to: directory to cache archives into, if an archive is
      available here it is not downloaded
    :param int timeout: timeout for downloading each individual archive when
      the connection becomes idle, no timeout applied if **None**
    :param int retries: maximum attempts to impose on a per-archive basis

    :returns: **iterator** of
      :class:`~stem.descriptor.bandwidth_file.BandwidthFile
      for the given time range

    :raises: :class:`~stem.DownloadFailed` if the download fails
    zbandwidth-filer   Nr   r   s           r   r)   zCollecTor.get_bandwidth_files<  sP     ( ZZ(%5 &&#3UC7^e&f $
r   c           	   #   |   K   | j                  d||      D ]"  }|j                  |d||||      D ]  }|  $ yw)a  
    `TorDNSEL exit lists <https://www.torproject.org/projects/tordnsel.html.en>`_
    for the given time range, sorted oldest to newest.

    :param datetime.datetime start: publication time to begin with
    :param datetime.datetime end: publication time to end with
    :param str cache_to: directory to cache archives into, if an archive is
      available here it is not downloaded
    :param int timeout: timeout for downloading each individual archive when
      the connection becomes idle, no timeout applied if **None**
    :param int retries: maximum attempts to impose on a per-archive basis

    :returns: **iterator** of
      :class:`~stem.descriptor.tordnsel.TorDNSEL
      for the given time range

    :raises: :class:`~stem.DownloadFailed` if the download fails
    tordnselr   Nr   r   s           r   r+   zCollecTor.get_exit_listsT  sN     ( ZZ
E3/ &&:ucWX_&` $
r   c                    | j                   r)t        j                         | j                  z
  t        k\  r@|dk(  rTt        j
                  t        j                  t        j                  t        j                  fD ]  }|j                  s|} n n|t        j                  }|t        j                  k7  r|j                  nd}t        dz   |z   }|j                  t        j                  j                  j!                  || j"                  | j$                              }t'        j(                  t        j                  j*                  j-                  |            | _         t        j                         | _        | j                   S )a  
    Provides the archives available in CollecTor.

    :param descriptor.Compression compression: compression type to
      download from, if undefiled we'll use the best decompression available

    :returns: **dict** with the archive contents

    :raises:
      If unable to retrieve the index this provide...

        * **ValueError** if json is malformed
        * **IOError** if unable to decompress
        * :class:`~stem.DownloadFailed` if the download fails
    best zindex/index.json)r   timer   REFRESH_INDEX_RATEr   r{   r|   r}   rb   	availabler   rp   rq   rP   rn   ro   rO   r   r   jsonloads	str_tools_to_unicode)r;   r3   optionr   urlrx   s         r   indexzCollecTor.indexl  s   " t/D/D!DHZ!Z		"''+:J:JKLaLab 	F K	 !+++6+:O:O+O+''UWi..:c''		(<(<(E(Ec4<<Y]YeYe(fgh::dii&9&9&E&Eh&OPd"iikdr   c           	         | j                   r(t        j                         | j                  z
  t        k\  r5t	        t
        j                  | j                         g       d       | _         g }| j                   D ]  }|r|j                  |j                  |k  r!|r|j                  |j                  |kD  r?|3t        |j                  D cg c]  }|j                  |       c}      st|j                  |        |S c c}w )a6  
    Provides files CollecTor presently has, sorted oldest to newest.

    :param str descriptor_type: descriptor type or prefix to retrieve
    :param datetime.datetime start: publication time to begin with
    :param datetime.datetime end: publication time to end with

    :returns: **list** of :class:`~stem.descriptor.collector.File`

    :raises:
      If unable to retrieve the index this provide...

        * **ValueError** if json is malformed
        * **IOError** if unable to decompress
        * :class:`~stem.DownloadFailed` if the download fails
    c                 >    | j                   r| j                   S t        S )N)r   FUTURE)xs    r   <lambda>z!CollecTor.files.<locals>.<lambda>  s    abahahVWV]V] nt r   )key)r   r   r   r   sortedr	   _filesr   r   r   anyr1   rS   append)r;   rX   r   r   matchesr   r   s          r   r   zCollecTor.files  s    $ t/D/D!DHZ!Z!)"2"24::<"DLtudG 	AEEMQUUU]AGGOqww}		 C`a`g`g(hS\)=)=o)N(h$iq N )is   D
c                 >   t        | t              sg S g }| j                         D ]  \  }}|dk(  r|D ]  }dj                  ||j	                  d      gz         }|j                  t        ||j	                  d      |j	                  d      |j	                  d      |j	                  d      |j	                  d      |j	                  d	                    |d
k(  s|D ]:  }|j                  t        j                  |||j	                  d      gz                <  |S )z
    Recursively provies files within the index.

    :param dict val: index hash
    :param list path: path we've transversed into

    :returns: **list** of :class:`~stem.descriptor.collector.File`
    r   r]   r/   r1   r4   r5   r<   r=   r8   directories)

isinstancedictitemsrF   getr   r-   extendr	   r   )valr/   r   kvattr	file_paths          r   r   zCollecTor._files  sD    c4 iE		 J1	
g 	uDhhttxx'7&889)
,,tItxx'8$((6:JDHHU]L^`d`h`hiz`{  ~B  ~F  ~F  GW  ~X  Z^  Zb  Zb  cr  Zs  t  u	u  	JD
,,y''ddhhv6F5G.GH
I	JJ Lr   )r   NNNNFNr   NNNNr   )r   )NNN)r   r   r   r   r>   r   r   r   r   r   r"   r'   r)   r+   r   r   r   r   r
   r   r   r	   r	     s    66 D #'dtXgXoXo{|  QV  af  rv  BC *X000!F D  r   r	   r   r   )$r   ri   rg   r6   rk   r   rG   rerM   rJ   r   stem.descriptorrP   stem.util.connectionstem.util.str_toolsr   r   rp   r   r   compiler   r   r   r   r   r   r   r   r"   r'   r)   r+   objectr-   r	   r
   r   r   <module>r      s   -^      	 	       83  BJJ./	2::CD 
		4A	&  dtP_PgPgst  IN  Y^  jn  z{ ]6 ]@} }r   