Skip to content

BUG: to_sql with datetime.time values with sqlite #11547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed

BUG: to_sql with datetime.time values with sqlite #11547

wants to merge 1 commit into from

Conversation

nbonnotte
Copy link
Contributor

I'm proposing a solution to #8341

As @jorisvandenbossche suggested, I use a sqlite3 adapter to transform datetime.time objects into string (hh:mm:ss.ffffff, as this what sqlalchemy... I think?)

I added a test in the class _TestSQLApi, so that the solution is tested with both sqlalchemy and the sqlite3 fallback. Thus, the result should be consistent.

@jorisvandenbossche jorisvandenbossche added the IO SQL to_sql, read_sql, read_sql_query label Nov 8, 2015
@jorisvandenbossche
Copy link
Member

@nbonnotte Thanks for working on this!

As a quick feedback: I think we will also want this to work in the other direction, so from sql -> python, that you get back datetime.time instances (so not only a 'adapter' but also 'converter' in sqlite terminology, https://docs.python.org/2/library/sqlite3.html#converting-sqlite-values-to-custom-python-types)

@nbonnotte
Copy link
Contributor Author

A quick update

@jorisvandenbossche to go the other way around is a bit more tricky, because (if i'm not mistaken) we depend on the sqlite3 connexion provided by the user.

Quick recap: in order for sqlite3 to know how to convert data back into the proper Python object, the user need to provide a parameter detect_types to sqlite3.connect specifying how to do that. There are 3 possibilities:

  1. The value sqlite3.PARSE_DECLTYPES tells sqlite3 to "parse" (how exactly?) the declared sql type
  2. The value sqlite3.PARSE_COLNAMES tells it to use the column name
  3. Not providing any value tells sqlite3 not to convert the data and let it as a string

Since the dataframe we want to sqlize most likely has names for its columns, I guess we can exclude option 2. For backward compatibility reason, option 3 needs to be kept working, and option 1 is the one we want to implement here.

From a very practical point of view, we thus need to have tests for both option 1 and option 3, because we don't know the connection the user will throw at us.

That's when some difficulties arise: in pandas/io/test_sql.py, just adding the option detect_types=sqlite3.PARSE_DECLTYPES in all sqlite3.connect without specifying any convertors causes errors for time and date objects. One is a bit surprising:

  File "/home/nicolas/Git/pandas/pandas/io/tests/test_sql.py", line 1923, in test_datetime_date
    tm.assert_frame_equal(res, df.astype(str))
...
DataFrame.iloc[:, 0] values are different (100.0 %)
[left]:  [2014-01-01, 2014-01-02]
[right]: [2014-01-01, 2014-01-02]

I hope it's just a matter of type conversion, that for some strange reason looks really really weird.

I haven't had much time to look precisely onto it yet, but going the other way around (sqlite → python) will definitely demand more work than my initial pull request (python → sqlite).

@nbonnotte
Copy link
Contributor Author

@jorisvandenbossche So basically, with the current pull request, the sqlite3 fallback should work exactly as with sqlalchemy.

To go further, that is to also provide a converter, would cause the "sqlite3 fallback" to have more features than with sqlalchemy, because then you would be able with the fallback to end up with, say, datetime objects, but not with sqlalchemy.

Am I wrong?

And assuming it is still an interesting direction to go, wouldn't it be better to have a new, separate issue, and let this PR close #8341?

@jreback
Copy link
Contributor

jreback commented Jan 6, 2016

@jorisvandenbossche status of this?

@jreback
Copy link
Contributor

jreback commented Jan 20, 2016

status of this?

@nbonnotte
Copy link
Contributor Author

@jorisvandenbossche Maybe we could let this PR close issue #8341, and start a new issue to go in the other direction (sql -> python) ? What do you think?

# this will transform time(12,34,56,789) into '12:34:56.000789'
# (this is what sqlalchemy does)
sqlite3.register_adapter(time, lambda _: _.strftime("%H:%M:%S.%f"))
super(self.__class__, self).__init__(*args, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this work correctly if inherited? You generally want super(SQLiteTable, self).__init__(*args, **kwargs)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, thanks

@jorisvandenbossche
Copy link
Member

@nbonnotte yes, certainly a good idea to limit it here to pandas->sql!

@nbonnotte
Copy link
Contributor Author

Great. And it's all green! \o/

def __init__(self, *args, **kwargs):
# GH 8341
# register an adapter callable for datetime.time object
# if we're here, it means we have sqlite3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sqlite3 is standard library, so the comment can be removed I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, thanks

@nbonnotte
Copy link
Contributor Author

Pull request updated and green

@jreback
Copy link
Contributor

jreback commented Feb 8, 2016

@jorisvandenbossche ?

@jreback jreback added this to the 0.18.0 milestone Feb 8, 2016
def __init__(self, *args, **kwargs):
# GH 8341
# register an adapter callable for datetime.time object
import sqlite3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this standard practice to do this in the init? (rather than at a class level)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea is to only have this adapter being registered if you actually use pandas with to_sql, not if you just import pandas to do other things (as this changes the behaviour of sqlite).

But actually, we should maybe test how this possibly interferes with sqlalchemy. @nbonnotte Can you test that writing/reading time values with sqlalchemy still works after having run this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nbonnotte did you for @jorisvandenbossche comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet, I'm a bit busy right now, but I've noted :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done... I think, see the test below

@jorisvandenbossche is it what you had in mind?

@jreback jreback modified the milestones: 0.18.1, 0.18.0 Feb 12, 2016
if self.flavor == 'sqlite':
self.assertRaises(sqlite3.InterfaceError, sql.to_sql, df,
'test_time', self.conn)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you leave this test here? (but the updated version of course with checking of the output)

@jorisvandenbossche
Copy link
Member

@nbonnotte Sorry for the delay, but some comments on the tests

@nbonnotte
Copy link
Contributor Author

I've updated the PR taking into account comments from @jorisvandenbossche

@jorisvandenbossche
Copy link
Member

Thanks @nbonnotte
I you put the whatsnew in 0.18.0, I think this can still be merged

@nbonnotte
Copy link
Contributor Author

Ok. If it can be shipped in 0.18.0, all the better — otherwise I'll change the whatsnew again.

All green.

@jorisvandenbossche
Copy link
Member

@nbonnotte merged, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
IO SQL to_sql, read_sql, read_sql_query
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants