7
7
import numpy as np
8
8
import traceback
9
9
10
+ import sqlite3
11
+ import warnings
12
+
10
13
from pandas .core .datetools import format as date_format
11
14
from pandas .core .api import DataFrame , isnull
15
+ from pandas .io import sql_legacy
12
16
13
17
#------------------------------------------------------------------------------
14
18
# Helper execution function
@@ -132,8 +136,85 @@ def uquery(sql, con=None, cur=None, retry=True, params=None):
132
136
return uquery (sql , con , retry = False )
133
137
return result
134
138
139
+ class SQLAlchemyRequired (Exception ):
140
+ pass
141
+
142
+ class LegacyMySQLConnection (Exception ):
143
+ pass
135
144
136
- def read_frame (sql , con , index_col = None , coerce_float = True , params = None ):
145
+ def get_connection (con , dialect , driver , username , password ,
146
+ host , port , database ):
147
+ if isinstance (con , basestring ):
148
+ try :
149
+ import sqlalchemy
150
+ return _alchemy_connect_sqlite (con )
151
+ except :
152
+ return sqlite3 .connect (con )
153
+ if isinstance (con , sqlite3 .Connection ):
154
+ return con
155
+ try :
156
+ import MySQLdb
157
+ except ImportError :
158
+ # If we don't have MySQLdb, this can't be a MySQLdb connection.
159
+ pass
160
+ else :
161
+ if isinstance (con , MySQLdb .connection ):
162
+ raise LegacyMySQLConnection
163
+ # If we reach here, SQLAlchemy will be needed.
164
+ try :
165
+ import sqlalchemy
166
+ except ImportError :
167
+ raise SQLAlchemyRequired
168
+ if isinstance (con , sqlalchemy .engine .Engine ):
169
+ return con .connect ()
170
+ if isinstance (con , sqlalchemy .engine .Connection ):
171
+ return con
172
+ if con is None :
173
+ url_params = (dialect , driver , username , \
174
+ password , host , port , database )
175
+ url = _build_url (* url_params )
176
+ engine = sqlalchemy .create_engine (url )
177
+ return engine .connect ()
178
+ if hasattr (con , 'cursor' ) and callable (con .cursor ):
179
+ # This looks like some Connection object from a driver module.
180
+ raise NotImplementedError , \
181
+ """To ensure robust support of varied SQL dialects, pandas
182
+ only supports database connections from SQLAlchemy. (Legacy
183
+ support for MySQLdb connections are available but buggy.)"""
184
+ else :
185
+ raise ValueError , \
186
+ """con must be a string, a Connection to a sqlite Database,
187
+ or a SQLAlchemy Connection or Engine object."""
188
+
189
+
190
+ def _alchemy_connect_sqlite (path ):
191
+ if path == ':memory:' :
192
+ return create_engine ('sqlite://' ).connect ()
193
+ else :
194
+ return create_engine ('sqlite:///%s' % path ).connect ()
195
+
196
+ def _build_url (dialect , driver , username , password , host , port , database ):
197
+ # Create an Engine and from that a Connection.
198
+ # We use a string instead of sqlalchemy.engine.url.URL because
199
+ # we do not necessarily know the driver; we know the dialect.
200
+ required_params = [dialect , username , password , host , database ]
201
+ for p in required_params :
202
+ if not isinstance (p , basestring ):
203
+ raise ValueError , \
204
+ "Insufficient information to connect to a database;" \
205
+ "see docstring."
206
+ url = dialect
207
+ if driver is not None :
208
+ url += "+%s" % driver
209
+ url += "://%s:%s@%s" % (username , password , host )
210
+ if port is not None :
211
+ url += ":%d" % port
212
+ url += "/%s" % database
213
+ return url
214
+
215
+ def read_sql (sql , con = None , index_col = None , flavor = None , driver = None ,
216
+ username = None , password = None , host = None , port = None ,
217
+ database = None , coerce_float = True , params = None ):
137
218
"""
138
219
Returns a DataFrame corresponding to the result set of the query
139
220
string.
@@ -145,32 +226,52 @@ def read_frame(sql, con, index_col=None, coerce_float=True, params=None):
145
226
----------
146
227
sql: string
147
228
SQL query to be executed
148
- con: DB connection object, optional
229
+ con : Connection object, SQLAlchemy Engine object, a filepath string
230
+ (sqlite only) or the string ':memory:' (sqlite only). Alternatively,
231
+ specify a user, passwd, host, and db below.
149
232
index_col: string, optional
150
233
column name to use for the returned DataFrame object.
234
+ flavor : string specifying the flavor of SQL to use
235
+ driver : string specifying SQL driver (e.g., MySQLdb), optional
236
+ username: username for database authentication
237
+ only needed if a Connection, Engine, or filepath are not given
238
+ password: password for database authentication
239
+ only needed if a Connection, Engine, or filepath are not given
240
+ host: host for database connection
241
+ only needed if a Connection, Engine, or filepath are not given
242
+ database: database name
243
+ only needed if a Connection, Engine, or filepath are not given
151
244
coerce_float : boolean, default True
152
245
Attempt to convert values to non-string, non-numeric objects (like
153
246
decimal.Decimal) to floating point, useful for SQL result sets
154
247
params: list or tuple, optional
155
248
List of parameters to pass to execute method.
156
249
"""
157
- cur = execute (sql , con , params = params )
158
- rows = _safe_fetch (cur )
159
- columns = [col_desc [0 ] for col_desc in cur .description ]
160
-
161
- cur .close ()
162
- con .commit ()
163
-
164
- result = DataFrame .from_records (rows , columns = columns ,
165
- coerce_float = coerce_float )
250
+ dialect = flavor
251
+ try :
252
+ connection = get_connection (con , dialect , driver , username , password ,
253
+ host , port , database )
254
+ except LegacyMySQLConnection :
255
+ warnings .warn ("For more robust support, connect using " \
256
+ "SQLAlchemy. See documentation." )
257
+ return sql_legacy .read_frame (sql , con , index_col , coerce_float , params )
258
+
259
+ if params is None :
260
+ params = []
261
+ cursor = connection .execute (sql , * params )
262
+ result = _safe_fetch (cursor )
263
+ columns = [col_desc [0 ] for col_desc in cursor .description ]
264
+ cursor .close ()
265
+
266
+ result = DataFrame .from_records (result , columns = columns )
166
267
167
268
if index_col is not None :
168
269
result = result .set_index (index_col )
169
270
170
271
return result
171
272
172
- frame_query = read_frame
173
- read_sql = read_frame
273
+ frame_query = read_sql
274
+ read_frame = read_sql
174
275
175
276
def write_frame (frame , name , con , flavor = 'sqlite' , if_exists = 'fail' , ** kwargs ):
176
277
"""
0 commit comments