|
36 | 36 | `PetalLength` DOUBLE,
|
37 | 37 | `PetalWidth` DOUBLE,
|
38 | 38 | `Name` VARCHAR(200)
|
| 39 | + )""", |
| 40 | + 'postgresql': """CREATE TABLE iris ( |
| 41 | + "SepalLength" DOUBLE PRECISION, |
| 42 | + "SepalWidth" DOUBLE PRECISION, |
| 43 | + "PetalLength" DOUBLE PRECISION, |
| 44 | + "PetalWidth" DOUBLE PRECISION, |
| 45 | + "Name" VARCHAR(200) |
39 | 46 | )"""
|
40 | 47 | },
|
41 | 48 | 'insert_iris': {
|
42 | 49 | 'sqlite': """INSERT INTO iris VALUES(?, ?, ?, ?, ?)""",
|
43 |
| - 'mysql': """INSERT INTO iris VALUES(%s, %s, %s, %s, "%s");""" |
| 50 | + 'mysql': """INSERT INTO iris VALUES(%s, %s, %s, %s, "%s");""", |
| 51 | + 'postgresql': """INSERT INTO iris VALUES(%s, %s, %s, %s, %s);""" |
44 | 52 | },
|
45 | 53 | 'create_test_types': {
|
46 | 54 | 'sqlite': """CREATE TABLE types_test_data (
|
|
62 | 70 | `BoolCol` BOOLEAN,
|
63 | 71 | `IntColWithNull` INTEGER,
|
64 | 72 | `BoolColWithNull` BOOLEAN
|
| 73 | + )""", |
| 74 | + 'postgresql': """CREATE TABLE types_test_data ( |
| 75 | + "TextCol" TEXT, |
| 76 | + "DateCol" TIMESTAMP, |
| 77 | + "IntDateCol" INTEGER, |
| 78 | + "FloatCol" DOUBLE PRECISION, |
| 79 | + "IntCol" INTEGER, |
| 80 | + "BoolCol" BOOLEAN, |
| 81 | + "IntColWithNull" INTEGER, |
| 82 | + "BoolColWithNull" BOOLEAN |
65 | 83 | )"""
|
66 | 84 | },
|
67 | 85 | 'insert_test_types': {
|
|
72 | 90 | 'mysql': """
|
73 | 91 | INSERT INTO types_test_data
|
74 | 92 | VALUES("%s", %s, %s, %s, %s, %s, %s, %s)
|
| 93 | + """, |
| 94 | + 'postgresql': """ |
| 95 | + INSERT INTO types_test_data |
| 96 | + VALUES(%s, %s, %s, %s, %s, %s, %s, %s) |
75 | 97 | """
|
76 | 98 | }
|
77 | 99 | }
|
@@ -403,29 +425,12 @@ def test_date_and_index(self):
|
403 | 425 | "IntDateCol loaded with incorrect type")
|
404 | 426 |
|
405 | 427 |
|
406 |
| -class TestSQLAlchemy(PandasSQLTest): |
407 |
| - |
408 |
| - ''' |
409 |
| - Test the sqlalchemy backend against an in-memory sqlite database. |
| 428 | +class _TestSQLAlchemy(PandasSQLTest): |
| 429 | + """ |
| 430 | + Base class for testing the sqlalchemy backend. Subclasses for specific |
| 431 | + database types are created below. |
410 | 432 | Assume that sqlalchemy takes case of the DB specifics
|
411 |
| - ''' |
412 |
| - flavor = 'sqlite' |
413 |
| - |
414 |
| - def connect(self): |
415 |
| - return sqlalchemy.create_engine('sqlite:///:memory:') |
416 |
| - |
417 |
| - def setUp(self): |
418 |
| - # Skip this test if SQLAlchemy not available |
419 |
| - if not SQLALCHEMY_INSTALLED: |
420 |
| - raise nose.SkipTest('SQLAlchemy not installed') |
421 |
| - |
422 |
| - self.conn = self.connect() |
423 |
| - self.pandasSQL = sql.PandasSQLAlchemy(self.conn) |
424 |
| - |
425 |
| - self._load_iris_data() |
426 |
| - self._load_raw_sql() |
427 |
| - |
428 |
| - self._load_test1_data() |
| 433 | + """ |
429 | 434 |
|
430 | 435 | def test_read_sql(self):
|
431 | 436 | self._read_sql_iris()
|
@@ -491,32 +496,31 @@ def test_read_table_absent(self):
|
491 | 496 | ValueError, sql.read_table, "this_doesnt_exist", con=self.conn)
|
492 | 497 |
|
493 | 498 | def test_default_type_convertion(self):
|
494 |
| - """ Test default type conversion""" |
495 | 499 | df = sql.read_table("types_test_data", self.conn)
|
496 |
| - self.assertTrue( |
497 |
| - issubclass(df.FloatCol.dtype.type, np.floating), "FloatCol loaded with incorrect type") |
498 |
| - self.assertTrue( |
499 |
| - issubclass(df.IntCol.dtype.type, np.integer), "IntCol loaded with incorrect type") |
500 |
| - self.assertTrue( |
501 |
| - issubclass(df.BoolCol.dtype.type, np.integer), "BoolCol loaded with incorrect type") |
| 500 | + |
| 501 | + self.assertTrue(issubclass(df.FloatCol.dtype.type, np.floating), |
| 502 | + "FloatCol loaded with incorrect type") |
| 503 | + self.assertTrue(issubclass(df.IntCol.dtype.type, np.integer), |
| 504 | + "IntCol loaded with incorrect type") |
| 505 | + self.assertTrue(issubclass(df.BoolCol.dtype.type, np.bool_), |
| 506 | + "BoolCol loaded with incorrect type") |
502 | 507 |
|
503 | 508 | # Int column with NA values stays as float
|
504 | 509 | self.assertTrue(issubclass(df.IntColWithNull.dtype.type, np.floating),
|
505 | 510 | "IntColWithNull loaded with incorrect type")
|
506 |
| - # Non-native Bool column with NA values stays as float |
507 |
| - self.assertTrue( |
508 |
| - issubclass(df.BoolColWithNull.dtype.type, np.floating), "BoolCol loaded with incorrect type") |
| 511 | + # Bool column with NA values becomes object |
| 512 | + self.assertTrue(issubclass(df.BoolColWithNull.dtype.type, np.object), |
| 513 | + "BoolColWithNull loaded with incorrect type") |
509 | 514 |
|
510 | 515 | def test_default_date_load(self):
|
511 | 516 | df = sql.read_table("types_test_data", self.conn)
|
512 | 517 |
|
513 | 518 | # IMPORTANT - sqlite has no native date type, so shouldn't parse, but
|
514 | 519 | # MySQL SHOULD be converted.
|
515 |
| - self.assertFalse( |
| 520 | + self.assertTrue( |
516 | 521 | issubclass(df.DateCol.dtype.type, np.datetime64), "DateCol loaded with incorrect type")
|
517 | 522 |
|
518 | 523 | def test_date_parsing(self):
|
519 |
| - """ Test date parsing """ |
520 | 524 | # No Parsing
|
521 | 525 | df = sql.read_table("types_test_data", self.conn)
|
522 | 526 |
|
@@ -551,6 +555,54 @@ def test_date_parsing(self):
|
551 | 555 | "IntDateCol loaded with incorrect type")
|
552 | 556 |
|
553 | 557 |
|
| 558 | +class TestSQLAlchemy(_TestSQLAlchemy): |
| 559 | + """ |
| 560 | + Test the sqlalchemy backend against an in-memory sqlite database. |
| 561 | + """ |
| 562 | + flavor = 'sqlite' |
| 563 | + |
| 564 | + def connect(self): |
| 565 | + return sqlalchemy.create_engine('sqlite:///:memory:') |
| 566 | + |
| 567 | + def setUp(self): |
| 568 | + # Skip this test if SQLAlchemy not available |
| 569 | + if not SQLALCHEMY_INSTALLED: |
| 570 | + raise nose.SkipTest('SQLAlchemy not installed') |
| 571 | + |
| 572 | + self.conn = self.connect() |
| 573 | + self.pandasSQL = sql.PandasSQLAlchemy(self.conn) |
| 574 | + |
| 575 | + self._load_iris_data() |
| 576 | + self._load_raw_sql() |
| 577 | + |
| 578 | + self._load_test1_data() |
| 579 | + |
| 580 | + def test_default_type_convertion(self): |
| 581 | + df = sql.read_table("types_test_data", self.conn) |
| 582 | + |
| 583 | + self.assertTrue(issubclass(df.FloatCol.dtype.type, np.floating), |
| 584 | + "FloatCol loaded with incorrect type") |
| 585 | + self.assertTrue(issubclass(df.IntCol.dtype.type, np.integer), |
| 586 | + "IntCol loaded with incorrect type") |
| 587 | + # sqlite has no boolean type, so integer type is returned |
| 588 | + self.assertTrue(issubclass(df.BoolCol.dtype.type, np.integer), |
| 589 | + "BoolCol loaded with incorrect type") |
| 590 | + |
| 591 | + # Int column with NA values stays as float |
| 592 | + self.assertTrue(issubclass(df.IntColWithNull.dtype.type, np.floating), |
| 593 | + "IntColWithNull loaded with incorrect type") |
| 594 | + # Non-native Bool column with NA values stays as float |
| 595 | + self.assertTrue(issubclass(df.BoolColWithNull.dtype.type, np.floating), |
| 596 | + "BoolColWithNull loaded with incorrect type") |
| 597 | + |
| 598 | + def test_default_date_load(self): |
| 599 | + df = sql.read_table("types_test_data", self.conn) |
| 600 | + |
| 601 | + # IMPORTANT - sqlite has no native date type, so shouldn't parse, but |
| 602 | + self.assertFalse(issubclass(df.DateCol.dtype.type, np.datetime64), |
| 603 | + "DateCol loaded with incorrect type") |
| 604 | + |
| 605 | + |
554 | 606 | # --- Test SQLITE fallback
|
555 | 607 | class TestSQLite(PandasSQLTest):
|
556 | 608 |
|
@@ -660,7 +712,7 @@ def tearDown(self):
|
660 | 712 | self.conn.close()
|
661 | 713 |
|
662 | 714 |
|
663 |
| -class TestMySQLAlchemy(TestSQLAlchemy): |
| 715 | +class TestMySQLAlchemy(_TestSQLAlchemy): |
664 | 716 | flavor = 'mysql'
|
665 | 717 |
|
666 | 718 | def connect(self):
|
@@ -691,13 +743,39 @@ def tearDown(self):
|
691 | 743 | for table in c.fetchall():
|
692 | 744 | self.conn.execute('DROP TABLE %s' % table[0])
|
693 | 745 |
|
694 |
| - def test_default_date_load(self): |
695 |
| - df = sql.read_table("types_test_data", self.conn) |
696 | 746 |
|
697 |
| - # IMPORTANT - sqlite has no native date type, so shouldn't parse, |
698 |
| - # but MySQL SHOULD be converted. |
699 |
| - self.assertTrue( |
700 |
| - issubclass(df.DateCol.dtype.type, np.datetime64), "DateCol loaded with incorrect type") |
| 747 | +class TestPostgreSQLAlchemy(_TestSQLAlchemy): |
| 748 | + flavor = 'postgresql' |
| 749 | + |
| 750 | + def connect(self): |
| 751 | + return sqlalchemy.create_engine( |
| 752 | + 'postgresql+{driver}://postgres@localhost/pandas_nosetest'.format(driver=self.driver)) |
| 753 | + |
| 754 | + def setUp(self): |
| 755 | + if not SQLALCHEMY_INSTALLED: |
| 756 | + raise nose.SkipTest('SQLAlchemy not installed') |
| 757 | + |
| 758 | + try: |
| 759 | + import psycopg2 |
| 760 | + self.driver = 'psycopg2' |
| 761 | + |
| 762 | + except ImportError: |
| 763 | + raise nose.SkipTest |
| 764 | + |
| 765 | + self.conn = self.connect() |
| 766 | + self.pandasSQL = sql.PandasSQLAlchemy(self.conn) |
| 767 | + |
| 768 | + self._load_iris_data() |
| 769 | + self._load_raw_sql() |
| 770 | + |
| 771 | + self._load_test1_data() |
| 772 | + |
| 773 | + def tearDown(self): |
| 774 | + c = self.conn.execute( |
| 775 | + "SELECT table_name FROM information_schema.tables" |
| 776 | + " WHERE table_schema = 'public'") |
| 777 | + for table in c.fetchall(): |
| 778 | + self.conn.execute("DROP TABLE %s" % table[0]) |
701 | 779 |
|
702 | 780 | if __name__ == '__main__':
|
703 | 781 | nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
|
|
0 commit comments