In [1]:
import sqlalchemy


from sqlalchemy import (create_engine,
                        Table,
                        Column,
                        Float,
                        String,
                        MetaData,
                        ForeignKey,
                        CheckConstraint)



def og_connect(con_string='sqlite:///test2.sqlite', echo=False):
    """og_connect(con_string='sqlite:///test2.sqlite', echo=False)
    
    Create a connection to a database and returns connection and metadata
    
    Parameters
    ----------
    con_string : str (default 'sqlite:///test2.sqlite')
                 connection string to a database, e.g.
                'sqlite:///test2.sqlite'
                 'postgresql://postgres@localhost/Dhole'
                 
                see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls for more info
    
    echo :      bool (default False)
                print database server log output
    
                 
    Returns
    -------
    eng : sqlalchemy engine  
        active connection to a database
    meta : sqlalchemy metadata
        container object that keeps together many different features of a database
    
    
    Example
    -------
    >>> con_string= 'postgresql://postgres@localhost/Dhole'
    >>> eng, meta = og_connect(con_string, echo=False)
    
    """


    #print 'connection string:', con_string
    #print 'echo', echo

    # get database type
    dbtype = con_string[0:5]
    if dbtype == 'sqlit':

        # Do a row connection and update some pragma
        eng = create_engine(con_string, echo=echo)
        with eng.connect() as con:
            print con.execute('PRAGMA foreign_keys = ON;')
            print con.execute('PRAGMA case_sensitive_like = True;')

    if dbtype == 'postg':
        eng = create_engine(con_string, echo=echo)

    if dbtype == 'sqlite':
        pass
        
    # create collar table
    meta = MetaData()
    meta.reflect(bind=eng)
    
    return eng, meta
    
    
    
def og_create_dhdef(eng, meta, dbsuffix="", collar_cols={}, survey_cols={}):
    """og_create_dhdef(eng, meta, dbsuffix="", collar_cols={}, survey_cols={})
    
    Create drillhole definition tables in the metadata, collar and survey.
    If you have more than one of drillhole use ``dbsuffix``, 
    e.g. ``dbsuffix= Historic`` will create tables 
    Historic_collar and Historic_survey
    
    Default culumns BHID, xcollar, ycollar, zcollar, LENGTH, and Comments
    will be automatically created. To add extra columns use ``collar_cols`` and 
    ``survey_cols``. Thise are dictionaries with column definition. Two options 
    are available: 
    
    a) To add a new column without external reference
        
        {Column1_name:{'coltypes':sqlalchemy.Data_Type, 'nullable': True/False}, 
         Column2_name:{'coltypes':sqlalchemy.Data_Type, 'nullable': True/False},
         ...
         ColumnN_name:{'coltypes':sqlalchemy.Data_Type, 'nullable': True/False}}
        
    b) To add a new column with external reference
    
        {Column1_name:{'coltypes':String, 
                       'nullable': True,
                       'foreignkey':{'column':reference_table.reference_column,
                                     'ondelete':'RESTRICT',
                                     'onupdate':'CASCADE'}}, 
        ...}    
    
    
    
    Parameters
    ----------
    eng : sqlalchemy engine  
            active connection to a database
            
    meta : sqlalchemy metadata
            container object that keeps together many different features of a database
    
    dbsuffix : str (default "")
            this suffix will be added to each table_name
    
    collar_cols : dict (default {})
            definition of new non default columns in table collar
            
    survey_cols : dict (default {})
            definition of new non default columns in table survey
    
    Example
    -------
    >>> og_create_dhdef(eng, meta, dbsuffix="Historic", 
                      collar_cols={'Company':{'coltypes':String, 'nullable': True}},
                      survey_cols={'Method' :{'coltypes':String, 'nullable': True}})
    
    """

    
    assert dbsuffix+'_collar' not in eng.table_names(), 'Collar table: {} already in database'.format(dbsuffix+'_collar')
    assert dbsuffix+'_survey' not in eng.table_names(), 'Surbey table: {} already in database'.format(dbsuffix+'_survey')
    
    collar = Table(dbsuffix+'_collar', meta,
                   Column('BHID', String, primary_key=True),
                   Column('xcollar', Float, nullable=False),
                   Column('ycollar', Float, nullable=False),
                   Column('zcollar', Float, nullable=False),
                   Column('LENGTH', Float, nullable=False),
                   Column('Comments', String))

    survey = Table(dbsuffix+'_survey', meta,
                   Column('BHID', None,
                          ForeignKey(column=dbsuffix+'_collar.BHID',
                                     ondelete='CASCADE',
                                     onupdate='CASCADE',
                                     name='chk_bhid'),
                          primary_key=True),
                   Column('at', Float, nullable=False, primary_key=True),
                   Column('az', Float, nullable=False),
                   Column('dip', Float, nullable=False),
                   Column('Comments', String))
    
    
    for ccol in collar_cols:
        if 'foreignkey' in collar_cols[ccol]:
            fk = ForeignKey(column=collar_cols[ccol]['foreignkey']['column'],
                            ondelete=collar_cols[ccol]['foreignkey']['ondelete'],
                            onupdate=collar_cols[ccol]['foreignkey']['onupdate'])
            tmpcol = Column(ccol, None, fk)
        else:
            tmpcol = Column(ccol, collar_cols[ccol]['coltypes'], nullable=collar_cols[ccol]['nullable'])

        collar.append_column(tmpcol)
    

    for scol in survey_cols:
        if 'foreignkey' in survey_cols[scol]:
            fk = ForeignKey(column=survey_cols[scol]['foreignkey']['column'],
                            ondelete=survey_cols[scol]['foreignkey']['ondelete'],
                            onupdate=survey_cols[scol]['foreignkey']['onupdate'])
            tmpcol = Column(scol, None, fk)
        else:
            tmpcol = Column(scol, survey_cols[scol]['coltypes'], nullable=survey_cols[scol]['nullable'])

        survey.append_column(tmpcol)    
    

def og_add_interval(eng, meta, table_name, cols={}, dbsuffix=""):
    """og_add_interval(eng, meta, table_name, cols={}, dbsuffix="")
    
    Create drillhole interval tables in the metadata, eg. assay or log.
    You may need the same ``dbsuffix`` used to create the table definitions.
    
    Default culumns BHID, FROM, TO, and Comments
    will be automatically created. To add extra columns use ``cols``, 
    a dictionary with column definition. Two options are available: 
    
    a) To add a new column without external reference
        
        {Column1_name:{'coltypes':sqlalchemy.Data_Type, 'nullable': True/False}, 
         Column2_name:{'coltypes':sqlalchemy.Data_Type, 'nullable': True/False},
         ...
         ColumnN_name:{'coltypes':sqlalchemy.Data_Type, 'nullable': True/False}}
        
    b) To add a new column with external reference
    
        {Column1_name:{'coltypes':String, 
                       'nullable': True,
                       'foreignkey':{'column':reference_table.reference_column,
                                     'ondelete':'RESTRICT',
                                     'onupdate':'CASCADE'}}, 
        ...}    
    
        
    Parameters
    ----------
    eng : sqlalchemy engine  
            active connection to a database
            
    meta : sqlalchemy metadata
            container object that keeps together many different features of a database
    
    table_name : str
            table name
    
    cols : dict (default {})
            definition of new non default columns
             
    Example
    -------
    >>> og_add_interval(eng, meta, 
                        table_name = 'assay', 
                        dbsuffix="Historic",
                        cols={'SampleID':{'coltypes':String, 
                                          'nullable': False,
                                          'foreignkey':{'column':'assay_certificate.SampleID',
                                                        'ondelete':'RESTRICT',
                                                        'onupdate':'CASCADE'}},
                             'Au_visual':{'coltypes':Float, 'nullable': True}})
    
    """
    # create interval table
    interval = Table(dbsuffix+'_'+table_name+'_int', meta,
                     Column('BHID', None,
                            ForeignKey(column=dbsuffix+'_collar.BHID',
                                       ondelete='CASCADE',
                                       onupdate='CASCADE',
                                       name='chk_bhid'),
                            primary_key=True),
                     Column('FROM', Float, nullable=False, primary_key=True),
                     Column('TO', Float, nullable=False),
                     Column('Comments', String),
                     CheckConstraint('"TO" > "FROM"', name='check_interv'))

    for col in cols:
        if 'foreignkey' in cols[col]:
            fk = ForeignKey(column=cols[col]['foreignkey']['column'],
                            ondelete=cols[col]['foreignkey']['ondelete'],
                            onupdate=cols[col]['foreignkey']['onupdate'])
            tmpcol = Column(col, None, fk)
        else:
            tmpcol = Column(col, cols[col]['coltypes'], nullable=cols[col]['nullable'])

        interval.append_column(tmpcol)


def og_references(eng, meta, table_name, key='SampleID', cols={}):
    """og_references(eng, meta, table_name, key='SampleID', cols={})
    
    Create reference tables in the metadata, eg. assay certificates or Lithology catalog.
    
    Reference tables will not use external references and columns may be formatted as:
            
        {Column1_name:{'coltypes':sqlalchemy.Data_Type, 'nullable': True/False}, 
         ...}
           
        
    Parameters
    ----------
    eng : sqlalchemy engine  
            active connection to a database
            
    meta : sqlalchemy metadata
            container object that keeps together many different features of a database
    
    table_name : str
            table name
    
    key : str (Default 'SampleID')
            name of the reference columns, it will be used as table key and 
            will not allow duplicates
    
    cols : dict (default {})
            definition of new non-default columns
             
    Example
    -------
    >>> og_references(eng, meta, table_name = 'assay_certificate', 
                      key = 'SampleID', cols={'Au':{'coltypes':Float, 'nullable': True}})
    >>> og_references(eng, meta, table_name = 'rock_catalog', 
                      key = 'RockID', cols={'Description':{'coltypes':String, 'nullable': True}})

    
    """
    
    # create interval table
    interval = Table(table_name, meta,
                     Column(key, String, primary_key=True),
                     Column('Comments', String))
    for col in cols:
        tmpcol = Column(col, cols[col]['coltypes'], nullable=cols[col]['nullable'])
        interval.append_column(tmpcol)


# TODO: add some functions to activate/disactivate constraints
# TODO: implement some triggers compatible (see dialects)???

def execute(eng, meta):
    """execute(eng, meta)
    
    Create all tables stored in the metadata ```meta``` in the database connected to ``eng``. 
    
    Parameters
    ----------
    eng : sqlalchemy engine  
            active connection to a database
            
    meta : sqlalchemy metadata
            container object that keeps together many different features of a database
    
    """    
    meta.create_all(eng)

In [14]:
con_string = 'sqlite:///smart_drillholes_gui/Test.sqlite'

eng, meta = og_connect(con_string, echo=False)


---------------------------------------------------------------------------
ArgumentError                             Traceback (most recent call last)
<ipython-input-14-40229de488ba> in <module>()
      1 con_string = 'sqlite://smart_drillholes_gui/Test.sqlite'
      2 
----> 3 eng, meta = og_connect(con_string, echo=False)

<ipython-input-1-217e6d5cf363> in og_connect(con_string, echo)
     55 
     56         # Do a row connection and update some pragma
---> 57         eng = create_engine(con_string, echo=echo)
     58         with eng.connect() as con:
     59             print con.execute('PRAGMA foreign_keys = ON;')

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/engine/__init__.pyc in create_engine(*args, **kwargs)
    385     strategy = kwargs.pop('strategy', default_strategy)
    386     strategy = strategies.strategies[strategy]
--> 387     return strategy.create(*args, **kwargs)
    388 
    389 

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/engine/strategies.pyc in create(self, name_or_url, **kwargs)
     89 
     90         # assemble connection arguments
---> 91         (cargs, cparams) = dialect.create_connect_args(u)
     92         cparams.update(pop_kwarg('connect_args', {}))
     93         cargs = list(cargs)  # allow mutability

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/dialects/sqlite/pysqlite.pyc in create_connect_args(self, url)
    357                 " sqlite:///:memory: (or, sqlite://)\n"
    358                 " sqlite:///relative/path/to/file.db\n"
--> 359                 " sqlite:////absolute/path/to/file.db" % (url,))
    360         filename = url.database or ':memory:'
    361         if filename != ':memory:':

ArgumentError: Invalid SQLite URL: sqlite://smart_drillholes_gui/Test.sqlite
Valid SQLite URL forms are:
 sqlite:///:memory: (or, sqlite://)
 sqlite:///relative/path/to/file.db
 sqlite:////absolute/path/to/file.db

In [10]:
eng, meta = og_connect(con_string)
og_references(eng, meta, table_name='assay_certificate', key='SampleID', cols={'Au': {'coltypes': Float,
                                                                                           'nullable': True}})
og_references(eng, meta, table_name='rock_catalog', key='RockID', cols={'Description': {'coltypes': String,
                                                                                                    'nullable': True}})
og_add_interval(eng, meta, table_name='assay', cols={'SampleID': {'coltypes': String,
                                                                              'nullable': False,
                                                                              'foreignkey': {'column': 'assay_certificate.SampleID',
                                                                                             'ondelete': 'RESTRICT',
                                                                                             'onupdate': 'CASCADE'}}})
og_add_interval(eng, meta, table_name='litho', cols={'RockID':{'coltypes': String,
                                                                           'nullable': True,
                                                                           'foreignkey': {'column': 'rock_catalog.RockID',
                                                                                          'ondelete': 'RESTRICT',
                                                                                          'onupdate': 'CASCADE'}}})
execute(eng, meta)


<sqlalchemy.engine.result.ResultProxy object at 0x7f3209291ed0>
<sqlalchemy.engine.result.ResultProxy object at 0x7f32105d6690>
---------------------------------------------------------------------------
NoReferencedTableError                    Traceback (most recent call last)
<ipython-input-10-81fe40810543> in <module>()
     14                                                                                           'ondelete': 'RESTRICT',
     15                                                                                           'onupdate': 'CASCADE'}}})
---> 16 execute(eng, meta)

<ipython-input-1-217e6d5cf363> in execute(eng, meta)
    323 
    324     """    
--> 325     meta.create_all(eng)

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/sql/schema.pyc in create_all(self, bind, tables, checkfirst)
   3858                           self,
   3859                           checkfirst=checkfirst,
-> 3860                           tables=tables)
   3861 
   3862     def drop_all(self, bind=None, tables=None, checkfirst=True):

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/engine/base.pyc in _run_visitor(self, visitorcallable, element, connection, **kwargs)
   1918                      connection=None, **kwargs):
   1919         with self._optional_conn_ctx_manager(connection) as conn:
-> 1920             conn._run_visitor(visitorcallable, element, **kwargs)
   1921 
   1922     class _trans_ctx(object):

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/engine/base.pyc in _run_visitor(self, visitorcallable, element, **kwargs)
   1527     def _run_visitor(self, visitorcallable, element, **kwargs):
   1528         visitorcallable(self.dialect, self,
-> 1529                         **kwargs).traverse_single(element)
   1530 
   1531 

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/sql/visitors.pyc in traverse_single(self, obj, **kw)
    119             meth = getattr(v, "visit_%s" % obj.__visit_name__, None)
    120             if meth:
--> 121                 return meth(obj, **kw)
    122 
    123     def iterate(self, obj):

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/sql/ddl.pyc in visit_metadata(self, metadata)
    710 
    711         collection = sort_tables_and_constraints(
--> 712             [t for t in tables if self._can_create_table(t)])
    713 
    714         seq_coll = [s for s in metadata._sequences.values()

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/sql/ddl.pyc in sort_tables_and_constraints(tables, filter_fn, extra_dependencies)
   1061                     continue
   1062 
-> 1063             dependent_on = fkc.referred_table
   1064             if dependent_on is not table:
   1065                 mutable_dependencies.add((dependent_on, table))

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/sql/schema.pyc in referred_table(self)
   2895 
   2896         """
-> 2897         return self.elements[0].column.table
   2898 
   2899     def _validate_dest_table(self, table):

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.pyc in __get__(self, obj, cls)
    752         if obj is None:
    753             return self
--> 754         obj.__dict__[self.__name__] = result = self.fget(obj)
    755         return result
    756 

/home/adrianmr/anaconda2/lib/python2.7/site-packages/sqlalchemy/sql/schema.pyc in column(self)
   1853                     "foreign key to target column '%s'" %
   1854                     (self.parent, tablekey, colname),
-> 1855                     tablekey)
   1856             elif parenttable.key not in parenttable.metadata:
   1857                 raise exc.InvalidRequestError(

NoReferencedTableError: Foreign key associated with column '_assay_int.BHID' could not find table '_collar' with which to generate a foreign key to target column 'BHID'

In [ ]: