From de4e38061a98ce7ec8fa2ef27a3b428e2cc3c4aa Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Fri, 27 Aug 2021 11:43:03 -0500 Subject: [PATCH 1/6] BUG: check is_named_tuple for objects that are not subclass of tuple #40682 --- pandas/core/dtypes/inference.py | 2 +- pandas/tests/io/test_sql.py | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/pandas/core/dtypes/inference.py b/pandas/core/dtypes/inference.py index 1360b66e77dc0..ae961e53d8b79 100644 --- a/pandas/core/dtypes/inference.py +++ b/pandas/core/dtypes/inference.py @@ -315,7 +315,7 @@ def is_named_tuple(obj) -> bool: >>> is_named_tuple((1, 2)) False """ - return isinstance(obj, tuple) and hasattr(obj, "_fields") + return isinstance(obj, abc.Sequence) and hasattr(obj, "_fields") def is_hashable(obj) -> bool: diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index 7f73b4f12c2fb..ec7750127ef41 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -2189,6 +2189,43 @@ def test_bigint_warning(self): with tm.assert_produces_warning(None): sql.read_sql_table("test_bigintwarning", self.conn) + def test_row_object_is_named_tuple(self): + # GH 40682 + # Test for the is_named_tuple() function + # Placed here due to its usage of sqlalchemy + + from sqlalchemy import ( + Column, + Integer, + String, + ) + from sqlalchemy.orm import ( + declarative_base, + sessionmaker, + ) + + BaseModel = declarative_base() + + class Test(BaseModel): + __tablename__ = "test_Frame" + id = Column(Integer, primary_key=True) + foo = Column(String(50)) + + BaseModel.metadata.create_all(self.conn) + Session = sessionmaker(bind=self.conn) + session = Session() + + df = pd.DataFrame({"id": [0, 1], "foo": ["hello", "world"]}) + df.to_sql("test_Frame", con=self.conn, index=False, if_exists="replace") + + session.commit() + foo = session.query(Test.id, Test.foo) + print(foo) + df = pd.DataFrame(foo) + session.close() + + assert list(df.columns) == ["id", "foo"] + class _TestMySQLAlchemy: """ From dbc5c2e13a54beff32bda7f2ee6e9154d508a935 Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Fri, 27 Aug 2021 11:59:08 -0500 Subject: [PATCH 2/6] fix import --- pandas/tests/io/test_sql.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index ec7750127ef41..acd60025c5b74 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -2215,13 +2215,13 @@ class Test(BaseModel): Session = sessionmaker(bind=self.conn) session = Session() - df = pd.DataFrame({"id": [0, 1], "foo": ["hello", "world"]}) + df = DataFrame({"id": [0, 1], "foo": ["hello", "world"]}) df.to_sql("test_Frame", con=self.conn, index=False, if_exists="replace") session.commit() foo = session.query(Test.id, Test.foo) print(foo) - df = pd.DataFrame(foo) + df = DataFrame(foo) session.close() assert list(df.columns) == ["id", "foo"] From fa7cd8ccd8c14a2c863d226c1e1de66b37e23d54 Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Fri, 27 Aug 2021 12:30:40 -0500 Subject: [PATCH 3/6] import compat --- pandas/tests/io/test_sql.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index acd60025c5b74..7624ab030bb5d 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -2199,10 +2199,12 @@ def test_row_object_is_named_tuple(self): Integer, String, ) - from sqlalchemy.orm import ( - declarative_base, - sessionmaker, - ) + from sqlalchemy.orm import sessionmaker + + if _gt14(): + from sqlalchemy.orm import declarative_base + else: + from sqlalchemy.ext.declarative import declarative_base BaseModel = declarative_base() From f0c07cce6e3ae0c1b4bc4ed3758c2cad434311fe Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Tue, 31 Aug 2021 16:01:04 -0500 Subject: [PATCH 4/6] cleanup, add doc --- doc/source/whatsnew/v1.4.0.rst | 1 + pandas/tests/io/test_sql.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index c3b27e7988d4a..396acddc7a0f3 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -335,6 +335,7 @@ I/O - Bug in :func:`json_normalize` where ``errors=ignore`` could fail to ignore missing values of ``meta`` when ``record_path`` has a length greater than one (:issue:`41876`) - Bug in :func:`read_csv` with multi-header input and arguments referencing column names as tuples (:issue:`42446`) - Bug in :func:`Series.to_json` and :func:`DataFrame.to_json` where some attributes were skipped when serialising plain Python objects to JSON (:issue:`42768`, :issue:`33043`) +- Column headers are dropped when constructing a :class:`DataFrame` from a sqlalchemy's `Row` object - Period diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index 7624ab030bb5d..eb3097618e158 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -2209,7 +2209,7 @@ def test_row_object_is_named_tuple(self): BaseModel = declarative_base() class Test(BaseModel): - __tablename__ = "test_Frame" + __tablename__ = "test_frame" id = Column(Integer, primary_key=True) foo = Column(String(50)) @@ -2218,11 +2218,10 @@ class Test(BaseModel): session = Session() df = DataFrame({"id": [0, 1], "foo": ["hello", "world"]}) - df.to_sql("test_Frame", con=self.conn, index=False, if_exists="replace") + df.to_sql("test_frame", con=self.conn, index=False, if_exists="replace") session.commit() foo = session.query(Test.id, Test.foo) - print(foo) df = DataFrame(foo) session.close() From 108d296568aef90c09252968d3556d452756aab2 Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Tue, 31 Aug 2021 16:01:39 -0500 Subject: [PATCH 5/6] cleanup, add doc --- doc/source/whatsnew/v1.4.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 396acddc7a0f3..63fa5906b7713 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -335,7 +335,7 @@ I/O - Bug in :func:`json_normalize` where ``errors=ignore`` could fail to ignore missing values of ``meta`` when ``record_path`` has a length greater than one (:issue:`41876`) - Bug in :func:`read_csv` with multi-header input and arguments referencing column names as tuples (:issue:`42446`) - Bug in :func:`Series.to_json` and :func:`DataFrame.to_json` where some attributes were skipped when serialising plain Python objects to JSON (:issue:`42768`, :issue:`33043`) -- Column headers are dropped when constructing a :class:`DataFrame` from a sqlalchemy's `Row` object +- Column headers are dropped when constructing a :class:`DataFrame` from a sqlalchemy's `Row` object (:issue:`40682`) - Period From 5e470222691d9caa261ef1064caf1b2d37ac7300 Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Tue, 31 Aug 2021 17:14:09 -0500 Subject: [PATCH 6/6] fix doc --- doc/source/whatsnew/v1.4.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 63fa5906b7713..5c61147524c48 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -335,7 +335,7 @@ I/O - Bug in :func:`json_normalize` where ``errors=ignore`` could fail to ignore missing values of ``meta`` when ``record_path`` has a length greater than one (:issue:`41876`) - Bug in :func:`read_csv` with multi-header input and arguments referencing column names as tuples (:issue:`42446`) - Bug in :func:`Series.to_json` and :func:`DataFrame.to_json` where some attributes were skipped when serialising plain Python objects to JSON (:issue:`42768`, :issue:`33043`) -- Column headers are dropped when constructing a :class:`DataFrame` from a sqlalchemy's `Row` object (:issue:`40682`) +- Column headers are dropped when constructing a :class:`DataFrame` from a sqlalchemy's ``Row`` object (:issue:`40682`) - Period