
`9Sc           @   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	 m
 Z
 d  d l	 m Z d  d l	 m Z d d g Z d f  d     YZ d f  d     YZ e d	  Z e d
  Z d   Z d   Z d S(   iN(   t   access(   t   excel(   t   txtt
   DataRecordt	   DataFieldc           B   s   e  Z d  Z d e d  Z RS(   s  Description of a class instance attribute mapped to a database field or
    optionally to a column of an Excel worksheet.

    Attributes:
      - 'attributeName': name of the attribute;
      - 'fieldName': name of the corresponding database field, or Excel file
        column header label (if the data are to be read from an Excel file);
      - 'dataType': name of the SQL data type for the database field (see
        'access.getDataTypeCode', 'access.getDataTypeName'). If the data are
        read from an Excel file instead of a database, this is used as a hint
        in cell format checking;
      - 'arrayDataType': Numpy array data type for array-like attributes. Not
        used unless 'dataType' is 'LONGBINARY'.
      - 'required': 'True' if this data field is mandatory for input database
        files and 'False' if the data field is optional. In the latter case,
        the value of the attribute would be set to 'None' if the corresponding
        database field is absent.c         C   s1   | |  _  | |  _ | |  _ | |  _ | |  _ d  S(   N(   t   attributeNamet	   fieldNamet   dataTypet   arrayDataTypet   required(   t   selfR   R   R   R   R	   (    (    s   code\common\DataRecord.pyt   __init__5   s
    				N(   t   __name__t
   __module__t   __doc__t   Nonet   TrueR   (    (    (    s   code\common\DataRecord.pyR   "   s   c           B   s:  e  Z d  Z d Z g  Z g  Z d Z d Z d Z	 d 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 e d    Z  e d    Z! d   Z" d   Z# RS(   s  Base class for data structures intended for serialization in Microsoft
    Access database files or in Microsoft Excel files.

    Primary class attributes:
      - 'tableName': string holding the name of the database table to store the
        data in (or the name of the Excel worksheet if the data are to be read
        from an Excel file);
      - 'fields': list of 'DataField' instances describing the structure of the
        data records;

    Class attributes related to operations with database files (see
    'createDatabase', 'checkDataFormat', 'readDataList', and 'writeData'):
      - 'extraFieldNames': list of names of required database fields that are
        not referenced in 'fields' (this is an empty list by default);
      - 'selectStatementExtraText': string to be appended to 'SELECT <fields>
        FROM <tableName>' command when querying the data from a database (this
        is an empty string by default);

      - 'createDatabaseErrorMessage': error string for database file creation
        failure;
      - 'openDatabaseErrorMessage': error string for failure opening a
        database file for reading;
      - 'queryFailureErrorMessage': error string for failure executing the SQL
        'SELECT' statement on an opened database file;
      - 'invalidFormatErrorPrefix': prefix for error strings reporting failed
        database structure tests, ending with a colon and space;
      - 'writeDataErrorMessage': error string for failure adding a new record
        to a database file;

    Class attributes related to reading data from an Excel file instead of a
    database (see 'readDataListFromExcel'):
      - 'extraExcelHeaderRows': number of Excel file rows that are located
        above the main header row and may contain some additional information
        that doesn't need to be checked by the file format testing procedure
        (this is zero by default);

      - 'openExcelFileErrorMessage': error string for failure opening an Excel
        file for reading;
      - 'invalidExcelFormatErrorPrefix': prefix for error strings reporting
        failed Excel file structure tests, ending with a colon and space;

    Class attributes related to Excel import and export operations (see
    'importDataListFromExcel', 'exportDataListToExcel' and
    'exportDataToExcel'):
      - 'importDataFromExcelErrorPrefix': prefix for error strings reporting
        failed Excel import;
      - 'exportDataToExcelErrorPrefix': prefix for error strings reporting
        failed Excel export.

    Primary class attributes ('tableName' and 'fields') must be redefined in
    the derived class. Class attributes related to error messages must be
    redefined unless code capable of generating an error will never be called.

    'onDataLoaded' virtual method may be redefined in order to add some
    custom processing to the data loading procedure.

    The differences between reading from an Excel file and importing from an
    Excel file are as follows:
      - Reading operation does not support array data fields (see
        'DataField.arrayDataType'), but is more flexible with regard to layout
        of the data cells: it allows to read Excel files with extra rows above
        the main header (see 'extraExcelHeaderRows'). On the other hand, Excel
        file format for import/export is fixed and optimized for export of
        databases containing array data fields. It will contain exactly the
        same number of data fields as defined in the 'fields' class attribute,
        and a special numbering column will be included in the main worksheet
        to facilitate location of the corresponding array data;
      - For clarity, error messages used in these two operations differ a
        bit.t    i    c         C   s+   x$ |  j    D] } t |  | d  q Wd S(   sK   Initialize all the attributes referenced in 'cls.fields' to
        'None'.N(   t   getAttributeNamest   setattrR   (   R
   t   name(    (    s   code\common\DataRecord.pyR      s    c         C   s   d S(   s   Called by 'readDataList' and similar functions after all the data
        record's attributes are read from a database or an Excel file.

        Redefine this method in the derived class to add any custom
        processing to the data loading procedure.N(    (   R
   (    (    s   code\common\DataRecord.pyt   onDataLoaded   s    c         C   s   g  |  j  D] } | j ^ q
 S(   s>   Return the list of attribute names referenced in 'cls.fields'.(   t   fieldsR   (   t   clst   field(    (    s   code\common\DataRecord.pyR      s    c         C   s&   g  |  j  D] } | j r
 | j ^ q
 S(   st   Return the list of attribute names referenced in 'cls.fields' whose
        'required' attributes are set to 'True'.(   R   R	   R   (   R   R   (    (    s   code\common\DataRecord.pyt   getRequiredAttributeNames   s    c         C   s.   x' |  j  D] } | j | k r
 | j Sq
 Wd S(   sa   Return the name of the database field corresponding to the attribute
        with the given name.N(   R   R   R   (   R   R   R   (    (    s   code\common\DataRecord.pyt   getFieldName   s    c         C   s   yd t  j |  } zC d j d   |  j D  } | j d t  j |  j  | f  Wd | j   XWn& t j	 k
 r t
 j |  j   n Xd S(   sZ   Create a new empty database file at the given path.

        Raise 'txt.Error' on failure.s   , c         s   s,   |  ]" } t  j | j  d  | j Vq d S(   t    N(   R    t	   quoteNameR   R   (   t   .0R   (    (    s   code\common\DataRecord.pys	   <genexpr>   s   s   CREATE TABLE %s (%s)N(   R    t   createDatabaset   joinR   t   ExecuteR   t	   tableNamet   Closet
   pywintypest	   com_errorR   t   Errort   createDatabaseErrorMessage(   R   t
   dbFilePatht   dbConnectiont   fieldsDefinition(    (    s   code\common\DataRecord.pyR      s    	c            s  y t  j |  } Wn& t j k
 r; t j |  j   n XySt  j |  } |  j | k r t j |  j	 d t j
 |  j    n  t  j | |  j    xi |  j g  |  j D] } | j r | j ^ q D]8 } |   k r t j |  j	 d t j
 |    q q Wy^ d j   f d   |  j D  } d | t  j |  j  f |  j } | j |  \ } }	 Wn& t j k
 rt j |  j   n XWn | j     n Xy | j }
 x |  j D] } | j p| j   k sqn  |
 j | j  j } | t  j | j  k rt  j | j  } | d k	 s1t  t j |  j	 d t j
 | j  | f   qqWWn | j   | j     n X| |   f S(   s   Same as 'checkDataFormat', but return a tuple of opened database
        connection, opened Recordset pointing to the data, and a list of the
        database field names.s   there is no table named %ss!   the database contains no %s fields   , c         3   s9   |  ]/ } | j  s! | j   k r t j | j  Vq d  S(   N(   R	   R   R    R   (   R   R   (   t   dbFieldNames(    s   code\common\DataRecord.pys	   <genexpr>   s   s   SELECT %s FROM %ss2   type of the %s database field is different from %sN(   R    t   openDatabaseR#   R$   R   R%   t   openDatabaseErrorMessaget   getTableNamesR!   t   invalidFormatErrorPrefixt   quotet   getFieldNamest   extraFieldNamesR   R	   R   R   R   t   selectStatementExtraTextR    t   queryFailureErrorMessageR"   t   Fieldst   Itemt   Typet   getDataTypeCodeR   t   getDataTypeNameR   t   AssertionError(   R   R'   R(   t   dbTableNamesR   R   t	   fieldListt   queryt   recSett   recordsAffectedt   dbFieldst   dbFieldTypeCodet   dataTypeName(    (   R*   s   code\common\DataRecord.pyt   _checkDataFormatImpl   sV    	-
	(

c         C   s0   |  j  |  \ } } } | j   | j   d S(   s   Assure that a file at the given path is a valid Access database and
        that its format matches the data structure.

        Raise 'txt.Error' on failure.N(   RB   R"   (   R   R'   R(   R=   R*   (    (    s   code\common\DataRecord.pyt   checkDataFormat+  s    
c         C   s  |  j  |  \ } } } zg  } x| j s| j } |    } xQ|  j D]F} | j | k rg d }	 n | j | j  j }	 | j d k r|	 d k	 rt	 |	 t
  s t  t j | j  j }
 t |	  |
 d k r t j d  }	 q|t j |	 d | j }	 nx | j d k r@|	 d k	 r@t j |	 j |	 j |	 j  }	 n< | j d k r||	 d k	 r|t j |	 j |	 j |	 j  }	 n  t | | j |	  qI W| j   | j |  | j    q$ WWd | j!   | j!   X| S(   sU  Open a database file at the given path for reading, check its
        format, query the available data and return them as a list of 'cls'
        instances.

        Raise 'txt.Error' on failure.

        Date and time values are stored as 'datetime.date' and 'datetime.time'
        instances; long binary values are stored as Numpy arrays.t
   LONGBINARYi    t   dtypet   DATEt   TIMEN("   RB   t   EOFR4   R   R   R   R5   t   ValueR   t
   isinstancet   bufferR9   t   numpyRE   R   t   itemsizet   lent   zerost
   frombuffert   datetimet   datet   yeart   montht   dayt   timet   hourt   minutet   secondR   R   R   t   appendt   MoveNextR"   (   R   R'   R(   R=   R*   t
   recordListR?   t
   dataRecordR   t   valuet   itemSize(    (    s   code\common\DataRecord.pyt   readDataList7  s>    			

c      	   C   s  y t  j | d t } Wn, t t  j f k
 rG t j |  j   n X|  j | j	   k r t j |  j
 d t j |  j    n  | j |  j  } |  j d } | j | } | d k  r t j |  j
 d t j |  j    n  i  } x t d | j  D] } | j | d |  } | j | d |  }	 |	 t  j k rGq n  | | k rt j |  j
 d t j t  j | d | |   t j t  j | d |   t j |  f   n  | | | <q Wxd |  j D]Y }
 |
 j r|
 j | k rt j |  j
 d t j |
 j  t j | j  f   qqWg  } x t |  D] } | | } |    } xg |  j D]\ }
 |
 j | k rt | | | |
 j |
 j |  j
 | j  } n d } t | |
 j |  qcW| j   | j  |  q@W| S(	   s   Same as 'readDataList', but read the data from a specially prepared
        Excel file.

        Warning: this function won't work if the data record contains
        array-like data fields (see 'DataField.arrayDataType').t	   on_demands   there is no worksheet named %si   i    s   %s worksheet contains no datas.   %s and %s header cells have the same name (%s)s4   there is no header cell named %s in the %s worksheetN(!   t   xlrdt   open_workbookR   t   IOErrort	   XLRDErrorR   R%   t   openExcelFileErrorMessageR!   t   sheet_namest   invalidExcelFormatErrorPrefixR/   t   sheet_by_namet   extraExcelHeaderRowst   nrowst   ranget   ncolst
   cell_valuet	   cell_typet   XL_CELL_TEXTt   cellnameR   R	   R   R   t   _readExcelCellR   t   datemodeR   R   R   R   RZ   (   R   t   excelFilePatht   workBookt   tableWorkSheett	   rowOffsett   recordCountt   fieldColumnIndicest   colt	   cellValuet   cellTypeR   R\   t   recordIndext   rowR]   R^   (    (    s   code\common\DataRecord.pyt   readDataListFromExcel  s`    !
	
c         C   s   t  j j |   d d S(   s?   Return the standard path to an Excel file used for data export.i    s   .xls(   t   ost   patht   splitext(   R'   (    (    s   code\common\DataRecord.pyt   getExcelExportFilePath  s    c      	   C   sH  | d } y t  j | d t } Wn- t t  j f k
 rR t j | d   n X|  j | j   k r t j | d t j	 |  j    n  | j
 |  j  } | j d k  r t j | d t j	 |  j    n  g  |  j D] } | j d k r | ^ q } g  |  j D] } | j d k r
| ^ q
} x> t t |   D]* }	 t | d |	 d | |	 j d	 |  q>Wg  }
 | j d } xt |  D]} | d } t | | d | d d
 |  |    } xZ t t |   D]F }	 t | | |	 d | |	 j | | j  } t | | |	 j |  qWt |  d k r)t t |  | |  \ } } | | j   k r{t j t d |   n  | j
 |  } x t t |   D] }	 t | d | |	 | d d
 |  t | d | |	 | |	 j d	 |  t | d | |	 | |	 j |  } t | | |	 j |  qWn  | j   |
 j |  qW|
 S(   sA   Same as 'importDataListFromExcel', but use a custom error prefix.s   file format is invalid: Ra   s4   the file is invalid or may not be opened for readings   there is no worksheet named %si   s   %s worksheet contains no dataRD   i    t   VARCHARt   INTi   (   Rb   Rc   R   Rd   Re   R   R%   R!   Rg   R/   Ri   Rk   R   R   Rl   RN   t   _testExcelCellR   Rr   Rs   R   R   t   _getArrayWorkSheett   errorPrefixt   _readExcelArrayR   R   RZ   (   R   Rt   t   errorMessagePrefixt   invalidFormatPrefixRu   Rv   R   t   scalarFieldst   arrayFieldst   iR\   t   totalRecordsR}   R~   R]   R^   t   arraySheetNamet   arrayColt   arrayWorkSheet(    (    s   code\common\DataRecord.pyt   _importDataListFromExcelImpl  sf    
			
		
c         C   s$   |  j  | |  j d t j |   S(   s   Reverse of 'exportDataListToExcel'.

        Similar to 'readDataList', but read the data from a strictly formed
        Excel file instead of a database.s    (%s): (   R   t   importDataFromExcelErrorPrefixR   t	   quotePath(   R   Rt   (    (    s   code\common\DataRecord.pyt   importDataListFromExcelA  s    		c      
   C   s  t  j   } | j |  j  } g  |  j D] } | j d k r( | ^ q( } g  |  j D] } | j d k rS | ^ qS } xd t t |   D]P } | j d | d | | j	  | | j d k r d | j
 | d  _ q q Wd }	 xTt t |   D]@}
 | |
 } |
 d } | j | d |
 d  x t t |   D] } t | | | j  } | | j d k r| d k	 rt  j   } d | _ | j | | d | |  q8| | j d k r| d k	 rt  j   } d	 | _ | j | | d | |  q8| j | | d |  q8Wt |  d k r t t |  |
 t |   \ } } |	 d k sb|	 j | k rt| j |  }	 n  x t t |   D] } |	 j d | | |
 d  |	 j d | | | | j	  t | | | j  } | d k	 rxB t t |   D]+ } |	 j | d
 | | t | |   qWqqWq q Wy | j |  Wn4 t k
 rt j |  j d t j |    n Xd S(   s   Write the given list of 'cls' instances to an Excel file compatible
        with 'importDataListFromExcel'.

        Raise 'txt.Error' on failure.RD   i    i   RF   i   i   s
   yyyy-mm-ddRG   s   hh:mm:ssi   s    (%s)Ni   (   t   xlwtt   Workbookt	   add_sheetR!   R   R   Rl   RN   t   writeR   Rz   t   widthR   t   getattrR   t   XFStylet   num_format_strR   R   t   floatt   savet	   ExceptionR   R%   t   exportDataToExcelErrorPrefixR   (   R   Rt   R\   Ru   Rv   R   R   R   R   R   R}   R]   R~   R^   t   styleR   R   t	   arrayData(    (    s   code\common\DataRecord.pyt   exportDataListToExcelL  s^    

		!#c         C   s  yt  j |  } zlt j j j d  } | j |  j | d d  z,g  |  j D] } | j	 ^ qP } g  } x |  j D] } t
 |  | j  } | j d k r | d
 k	 r t | j    } n | j d k r| d
 k	 rt j | j | j | j d d d f  } nH | j d k rM| d
 k	 rMt j d d d	 | j | j | j f  } n  | j |  qu W| j | |  Wd
 | j   XWd
 | j   XWn& t j k
 rt j |  j   n Xd
 S(   s   Append the 'self' data record to a database file at the given path.

        Raise 'txt.Error' on failure.

        Note: no thorough examination of the database file structure is carried
        out; use 'checkDataFormat' for that.s   ADODB.Recordseti    i   RD   RF   RG   ik  i   i   N(   R    R+   t   win32comt   clientt   gencachet   EnsureDispatcht   OpenR!   R   R   R   R   R   R   RK   t   tostringR#   t   TimeRS   RT   RU   RW   RX   RY   RZ   t   AddNewR"   R$   R   R%   t   writeDataErrorMessage(   R
   R'   R(   R=   R   R;   t	   valueListR^   (    (    s   code\common\DataRecord.pyt	   writeData  s2    	'c         C   sb   t  j j |  r; |  j | |  j d t j |   } n g  } | j |   |  j | |  d S(   s   Append the 'self' data record to an Excel file at the given path.

        Raise 'txt.Error' on failure.

        Warning: this function actually imports the entire file and then writes
        the data back, thus being quite time-consuming on large files.s    (%s): N(	   R   R   t   existsR   R   R   R   RZ   R   (   R
   Rt   R\   (    (    s   code\common\DataRecord.pyt   exportDataToExcel  s    			N($   R   R   R   R   R!   R   R1   R2   R&   R,   R3   R.   R   Rj   Rf   Rh   R   R   R   R   t   classmethodR   R   R   R   RB   RC   R`   R   t   staticmethodR   R   R   R   R   R   (    (    (    s   code\common\DataRecord.pyR   ?   s>   E				SHXc`	<c   	      C   s  |  j  | k s |  j | k r_ t j | d t j t j | |   t j |  j  f   n  |  j | |  } |  j	 | |  } | t j
 k r d S| d k rt j | | |  } | d k rt j | d t j t j | |   t j |  j  f   qn| d k rxt j | | |  } | d k rt j | d t j t j | |   t j |  j  f   qn2| d k rE| t j k rt j | d
 t j t j | |   t j |  j  f   n  | d k r<t |  } | | k rBt j | d t j t j | |   t j |  j  f   qBq| } ne | d k r| t j k rt j | d t j t j | |   t j |  j  f   n  | } n  | S(   s   Assure that the content of an Excel cell is consistent with the given
    SQL data type and return the cell value.

    Raise 'txt.Error' on failure. 'workBookDateMode' has to be the
    workbook's 'datemode' attribute for date and time values.s,   %s cell in the %s worksheet contains no dataRF   s/   %s cell in the %s worksheet is not a valid dateRG   s/   %s cell in the %s worksheet is not a valid timeR   t   SMALLINTt   SINGLEt   DOUBLEs1   %s cell in the %s worksheet is not a valid numbers3   %s cell value in the %s worksheet is not an integerR   s/   %s cell in the %s worksheet is not a text fieldN(   s   INTs   SMALLINTs   SINGLEs   DOUBLE(   s   INTs   SMALLINT(   Rk   Rm   R   R%   R/   Rb   Rq   R   Rn   Ro   t   XL_CELL_EMPTYR   R   t   readDatet   readTimet   XL_CELL_NUMBERt   intRp   (	   t	   workSheetR~   Rz   R   R   t   workBookDateModeR{   R|   R^   (    (    s   code\common\DataRecord.pyRr     sX    		5	 	 		 			c         C   s   t  |  | | | | |  | k r | d k r< t |  } n  t j | d t j t j | |   t j |  j  t j |  f   n  d S(   s   Assure that the given Excel cell has the given value consistent with
    the given SQL data type.

    Raise 'txt.Error' on failure. 'workBookDateMode' has to be the
    workbook's 'datemode' attribute for date and time values.R   s6   %s cell value in the %s worksheet is different from %sN(   Rr   t   strR   R%   R/   Rb   Rq   R   (   R   R~   Rz   t   requiredValuet   requiredTypeR   R   (    (    s   code\common\DataRecord.pyR   F  s    	'c   	      C   s   d |  } | | } | | |  } | | k r7 d } nN | | d } t  | d | |  } | | k ru d | } n d | | f } | | f S(   s8  Calculate the name of the Excel worksheet to store array-like field
    data in and the starting data column for the given data record.

    Attributes:
      - 'arrayCount': number of array-like database fields;
      - 'recordIndex': zero-based index of the database record;
      - 'totalRecords': total number of data records in the database.

    Return values:
      - 'sheetName': name of the worksheet to store the array-like data in;
      - 'arrayColumn': starting column for the array-like data of the given
        database record in 'sheetName' worksheet.i   t   Arraysi   s   Arrays (%d)u   Arrays (%d–%d)(   t   min(	   t
   arrayCountR}   R   t   recordsPerSheett
   sheetIndext   arrayColumnt	   sheetNamet   firstRecordt
   lastRecord(    (    s   code\common\DataRecord.pyR   Z  s    

	c   	      C   s   g  } x t  | |  j  D] } |  j | |  } |  j | |  } | t j k rV Pn  | t j k r t j | d t j	 t j
 | |   t j	 |  j  f   n  | j |  q Wt |  d k r d St j | d | Sd S(   s+  Read a numeric array stored in a column of the given Excel worksheet and
    return it as a Numpy array with the given data type.

    The array starts at 'startingRow' and spans downwards either up to the
    bottom of the worksheet or up to the first empty cell.

    Raise 'txt.Error' on failure.s1   %s cell in the %s worksheet is not a valid numberi    RE   N(   Rl   Rk   Rn   Ro   Rb   R   R   R   R%   R/   Rq   R   RZ   RN   R   RL   t   array(	   R   t   startingRowRz   R   R   R   R~   R{   R|   (    (    s   code\common\DataRecord.pyR     s    
	(   RQ   RL   R   t   os.pathR#   t   win32com.clientR   Rb   R   t   common.utilsR    R   R   t   __all__R   R   R   Rr   R   R   R   (    (    (    s   code\common\DataRecord.pyt   <module>   s&     G	%