pandas-query-sql 0.8.0__tar.gz → 0.8.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,21 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pandas_query_sql
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: sqldf for pandas
5
- Author-Email: Alexander Plavin <alexander@plav.in>, Greg Lamp <lamp.greg@gmail.com>, Michael Dempsey <bluestealth@bluestealth.pw>
6
- License: MIT
7
- Classifier: License :: OSI Approved :: MIT License
8
- Project-URL: Homepage, https://github.com/bluestealth/pandasql/
9
- Requires-Python: >=3.8
5
+ Author: Alexander Plavin, Greg Lamp, Michael Dempsey
6
+ Author-email: Alexander Plavin <alexander@plav.in>, Greg Lamp <lamp.greg@gmail.com>, Michael Dempsey <bluestealth@bluestealth.pw>
7
+ License-Expression: MIT
8
+ License-File: LICENSE.txt
10
9
  Requires-Dist: numpy
11
10
  Requires-Dist: packaging
12
11
  Requires-Dist: pandas
13
12
  Requires-Dist: sqlalchemy
14
- Requires-Dist: psycopg2-binary; extra == "postgresql"
15
- Requires-Dist: black; extra == "linters"
16
- Requires-Dist: ruff; extra == "linters"
17
- Requires-Dist: pytest; extra == "testing"
18
- Requires-Dist: psycopg2-binary; extra == "testing"
13
+ Requires-Dist: psycopg2 ; extra == 'postgresql'
14
+ Requires-Dist: psycopg2-binary ; extra == 'postgresql-binary'
15
+ Requires-Python: >=3.10
16
+ Project-URL: Homepage, https://github.com/bluestealth/pandasql/
19
17
  Provides-Extra: postgresql
20
- Provides-Extra: linters
21
- Provides-Extra: testing
18
+ Provides-Extra: postgresql-binary
22
19
  Description-Content-Type: text/markdown
23
20
 
24
21
  pandasql
@@ -0,0 +1,74 @@
1
+ [project]
2
+ name = "pandas_query_sql"
3
+ version = "0.8.2"
4
+ description = "sqldf for pandas"
5
+ authors = [
6
+ {name = "Alexander Plavin", email = "alexander@plav.in"},
7
+ {name = "Greg Lamp", email= "lamp.greg@gmail.com"},
8
+ {name = "Michael Dempsey", email = "bluestealth@bluestealth.pw"},
9
+ ]
10
+ dependencies = [
11
+ "numpy",
12
+ "packaging",
13
+ "pandas",
14
+ "sqlalchemy",
15
+ ]
16
+ requires-python = ">=3.10"
17
+ readme = "README.md"
18
+ license = "MIT"
19
+ license-files = ["LICENSE.txt"]
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/bluestealth/pandasql/"
23
+
24
+ [project.optional-dependencies]
25
+ postgresql = [
26
+ "psycopg2",
27
+ ]
28
+ postgresql-binary = [
29
+ "psycopg2-binary",
30
+ ]
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "ruff",
35
+ "pytest",
36
+ "psycopg2-binary",
37
+ "testcontainers[postgres]",
38
+ 'ty',
39
+ ]
40
+
41
+ [build-system]
42
+ requires = ["uv_build>=0.11.28,<0.12"]
43
+ build-backend = "uv_build"
44
+
45
+ [tool.uv.build-backend]
46
+ module-name = "pandasql"
47
+
48
+ [tool.ty.src]
49
+ exclude = ["examples/**"]
50
+
51
+ [tool.ruff]
52
+ line-length = 120
53
+ target-version = 'py310'
54
+
55
+ [tool.ruff.lint]
56
+ select = ['ALL']
57
+ ignore = ['COM812', 'FBT001', 'FBT002', 'TRY003', 'EM101', 'PLR2004', 'N818']
58
+ flake8-quotes = {}
59
+ isort = { known-first-party = [] }
60
+ mccabe = { max-complexity = 14 }
61
+ pydocstyle = { convention = 'google' }
62
+
63
+ [tool.ruff.format]
64
+ quote-style = "double"
65
+ indent-style = "space"
66
+
67
+ [tool.ruff.lint.per-file-ignores]
68
+ 'src/pandasql/__init__.py' = ['D']
69
+ "examples/**/*.py" = ['D', 'INP001']
70
+ "tests/**/*.py" = ['D', 'BLE001']
71
+
72
+ [tool.ruff.lint.extend-per-file-ignores]
73
+ "examples/**/*.py" = ['T']
74
+ "tests/**/*.py" = ['T', 'S101', 'PD901', 'PLR']
@@ -1,4 +1,4 @@
1
- import os
1
+ import pathlib
2
2
 
3
3
  import pandas as pd
4
4
 
@@ -6,11 +6,11 @@ from .sqldf import PandaSQL, PandaSQLException, sqldf
6
6
 
7
7
  __all__ = ["PandaSQL", "PandaSQLException", "sqldf"]
8
8
 
9
- _ROOT = os.path.abspath(os.path.dirname(__file__))
9
+ _ROOT = pathlib.Path(__file__).absolute().parent
10
10
 
11
11
 
12
- def get_data(path: str) -> str:
13
- return os.path.join(_ROOT, "data", path)
12
+ def get_data(path: str) -> pathlib.Path:
13
+ return _ROOT.joinpath("data", path)
14
14
 
15
15
 
16
16
  def load_meat() -> pd.DataFrame:
@@ -4,43 +4,46 @@ import importlib.metadata
4
4
  import inspect
5
5
  import re
6
6
  import sqlite3
7
+ from collections.abc import Iterator
7
8
  from contextlib import contextmanager
8
- from typing import Any, Dict, Iterator, Optional, Set, Union
9
+ from typing import Any
9
10
  from warnings import catch_warnings, filterwarnings
10
11
 
11
12
  import packaging.version
12
13
  from pandas import DataFrame
14
+ from pandas.errors import DatabaseError as PandasDatabaseError
13
15
  from pandas.io.sql import read_sql_query
14
- from sqlalchemy import create_engine
16
+ from sqlalchemy import create_engine # pyright: ignore[reportUnknownVariableType]
15
17
  from sqlalchemy.engine import Connection, Engine
16
- from sqlalchemy.event import listen
17
- from sqlalchemy.exc import DatabaseError, ResourceClosedError
18
+ from sqlalchemy.event import listen # pyright: ignore[reportUnknownVariableType]
19
+ from sqlalchemy.exc import DatabaseError as SQLAlchemyDatabaseError
20
+ from sqlalchemy.exc import ResourceClosedError
18
21
  from sqlalchemy.pool import NullPool
19
- from sqlalchemy.pool.base import _ConnectionRecord
22
+ from sqlalchemy.pool.base import (
23
+ _ConnectionRecord, # pyright: ignore[reportPrivateUsage]
24
+ )
20
25
 
21
26
  __all__ = ["PandaSQL", "PandaSQLException", "sqldf"]
22
27
 
23
28
  SQLALCHEMY_MAJOR_VERSION = packaging.version.Version(
24
- importlib.metadata.version("sqlalchemy")
29
+ importlib.metadata.version("sqlalchemy"),
25
30
  ).major
26
31
 
27
32
 
28
33
  class PandaSQLException(Exception):
29
34
  """Exception Class for PandaSQL."""
30
35
 
31
- pass
32
-
33
36
 
34
37
  class PandaSQL:
35
38
  """Class for using SQL database to query pandas DataFrames.
36
39
 
37
40
  Attributes:
38
41
  engine: sqlalchemy database engine
39
- persist: boolean to determine if tables stay loaded between function calls
42
+ persist: boolean to determine if tables stay loaded between function calls
40
43
  loaded_tables: set of currently loaded table names
41
44
  """
42
45
 
43
- def __init__(self, db_uri: Optional[str], persist: bool = False) -> None:
46
+ def __init__(self, db_uri: str | None, persist: bool = False) -> None:
44
47
  """Initialize with a specific database.
45
48
 
46
49
  :param db_uri: SQLAlchemy-compatible database URI.
@@ -49,7 +52,7 @@ class PandaSQL:
49
52
  if not db_uri:
50
53
  db_uri = "sqlite:///:memory:"
51
54
 
52
- self.engine = create_engine(url=db_uri, poolclass=NullPool)
55
+ self.engine: Engine = create_engine(url=db_uri, poolclass=NullPool)
53
56
 
54
57
  if self.engine.name == "sqlite":
55
58
  listen(target=self.engine, identifier="connect", fn=self._set_text_factory)
@@ -57,18 +60,20 @@ class PandaSQL:
57
60
  pass
58
61
  else:
59
62
  raise PandaSQLException(
60
- "Currently only sqlite and postgresql are supported."
63
+ "Currently only sqlite and postgresql are supported.",
61
64
  )
62
65
 
63
66
  self.persist = persist
64
- self.loaded_tables: Set[str] = set()
67
+ self.loaded_tables: set[str] = set()
65
68
  if self.persist:
66
69
  self._conn = self.engine.connect()
67
70
  self._init_connection(self._conn)
68
71
 
69
72
  def __call__(
70
- self, query: str, env: Optional[Dict[str, Any]] = None
71
- ) -> Optional[DataFrame]:
73
+ self,
74
+ query: str,
75
+ env: dict[str, Any] | None = None,
76
+ ) -> DataFrame | None:
72
77
  """Execute the SQL query.
73
78
 
74
79
  Automatically creates tables mentioned in the query from dataframes before executing.
@@ -94,11 +99,11 @@ class PandaSQL:
94
99
 
95
100
  try:
96
101
  result = read_sql_query(sql=query, con=conn)
97
- except DatabaseError as ex:
98
- raise PandaSQLException(ex)
99
102
  except ResourceClosedError:
100
103
  # query returns nothing
101
104
  result = None
105
+ except (SQLAlchemyDatabaseError, PandasDatabaseError) as ex:
106
+ raise PandaSQLException(ex) from ex
102
107
 
103
108
  return result
104
109
 
@@ -123,48 +128,54 @@ class PandaSQL:
123
128
  def _init_connection(self, conn: Connection) -> None:
124
129
  if self.engine.name == "postgresql":
125
130
  if SQLALCHEMY_MAJOR_VERSION >= 2:
126
- from sqlalchemy import text
131
+ from sqlalchemy import text # noqa: PLC0415
127
132
 
128
- conn.execute(statement=text("set search_path to pg_temp"))
133
+ conn.execute( # pyright: ignore[reportUnknownMemberType]
134
+ statement=text("set search_path to pg_temp"),
135
+ )
129
136
  else:
130
- conn.execute(statement="set search_path to pg_temp")
137
+ conn.execute( # ty: ignore[no-matching-overload]
138
+ statement="set search_path to pg_temp",
139
+ )
131
140
 
132
141
  def _set_text_factory(
133
- self, dbapi_conn: sqlite3.Connection, _: _ConnectionRecord
142
+ self,
143
+ dbapi_conn: sqlite3.Connection,
144
+ _: _ConnectionRecord,
134
145
  ) -> None:
135
146
  dbapi_conn.text_factory = str
136
147
 
137
148
 
138
- def get_outer_frame_variables() -> Dict[str, Any]:
149
+ def get_outer_frame_variables() -> dict[str, Any]:
139
150
  """Get a dict of local and global variables of the first outer frame from another file."""
140
151
  frame = inspect.currentframe()
141
- variables: Dict[str, Any] = {}
152
+ variables: dict[str, Any] = {}
142
153
 
143
154
  if not frame:
144
155
  return variables
145
156
 
146
157
  cur_filename = inspect.getframeinfo(frame).filename
147
- outer_frame = next(
148
- f
149
- for f in inspect.getouterframes(inspect.currentframe())
150
- if f.filename != cur_filename
151
- )
158
+ outer_frame = next(f for f in inspect.getouterframes(inspect.currentframe()) if f.filename != cur_filename)
152
159
  variables.update(outer_frame.frame.f_globals)
153
160
  variables.update(outer_frame.frame.f_locals)
154
161
  return variables
155
162
 
156
163
 
157
- def extract_table_names(query: str) -> Set[str]:
164
+ def extract_table_names(query: str) -> set[str]:
158
165
  """Extract table names from an SQL query."""
159
166
  # a good old fashioned regex. turns out this worked better than actually parsing the code
160
167
  tables_blocks = re.findall(
161
- r"(?:FROM|JOIN)\s+(\w+(?:\s*,\s*\w+)*)", query, re.IGNORECASE
168
+ r"(?:FROM|JOIN)\s+(\w+(?:\s*,\s*\w+)*)",
169
+ query,
170
+ re.IGNORECASE,
162
171
  )
163
172
  return {tbl for block in tables_blocks for tbl in re.findall(r"\w+", block)}
164
173
 
165
174
 
166
175
  def write_table(
167
- df: DataFrame, tablename: str, conn: Union[Engine, Connection, sqlite3.Connection]
176
+ df: DataFrame,
177
+ tablename: str,
178
+ conn: Engine | Connection | sqlite3.Connection,
168
179
  ) -> None:
169
180
  """Write Pandas DataFrame to SQL Table.
170
181
 
@@ -184,13 +195,18 @@ def write_table(
184
195
  df.to_sql(
185
196
  name=tablename,
186
197
  con=conn,
187
- index=not any(name is None for name in df.index.names), # type: ignore
198
+ index=not any(
199
+ name is None # pyright: ignore[reportUnnecessaryComparison]
200
+ for name in df.index.names # pyright: ignore[reportUnknownMemberType]
201
+ ),
188
202
  ) # load index into db if all levels are named
189
203
 
190
204
 
191
205
  def sqldf(
192
- query: str, env: Optional[Dict[str, Any]] = None, db_uri: Optional[str] = None
193
- ) -> Optional[DataFrame]:
206
+ query: str,
207
+ env: dict[str, Any] | None = None,
208
+ db_uri: str | None = None,
209
+ ) -> DataFrame | None:
194
210
  """Query pandas data frames using sql syntax.
195
211
 
196
212
  This function is meant for backward compatibility only. New users are encouraged to use the PandaSQL class.
@@ -1,11 +0,0 @@
1
- ##Authors
2
-
3
- - Greg Lamp
4
- - Austin Ogilvie
5
-
6
- ##Patches
7
- - Colin Dickson
8
- - stonebig
9
-
10
- ##Fork author
11
- - Alexander Plavin
@@ -1,109 +0,0 @@
1
- [project]
2
- name = "pandas_query_sql"
3
- version = "0.8.0"
4
- description = "sqldf for pandas"
5
- authors = [
6
- { name = "Alexander Plavin", email = "alexander@plav.in" },
7
- { name = "Greg Lamp", email = "lamp.greg@gmail.com" },
8
- { name = "Michael Dempsey", email = "bluestealth@bluestealth.pw" },
9
- ]
10
- dependencies = [
11
- "numpy",
12
- "packaging",
13
- "pandas",
14
- "sqlalchemy",
15
- ]
16
- requires-python = ">=3.8"
17
- readme = "README.md"
18
- classifiers = [
19
- "License :: OSI Approved :: MIT License",
20
- ]
21
-
22
- [project.license]
23
- text = "MIT"
24
-
25
- [project.urls]
26
- Homepage = "https://github.com/bluestealth/pandasql/"
27
-
28
- [project.optional-dependencies]
29
- postgresql = [
30
- "psycopg2-binary",
31
- ]
32
- linters = [
33
- "black",
34
- "ruff",
35
- ]
36
- testing = [
37
- "pytest",
38
- "psycopg2-binary",
39
- ]
40
-
41
- [build-system]
42
- requires = [
43
- "pdm-backend",
44
- ]
45
- build-backend = "pdm.backend"
46
-
47
- [tool.pdm]
48
- distribution = true
49
-
50
- [tool.pdm.package-dir]
51
- pandasql = "pandasql"
52
-
53
- [tool.setuptools.package-data]
54
- pandasql = [
55
- "data/births.csv",
56
- "data/birth_by_months.csv",
57
- "data/meat.csv",
58
- ]
59
-
60
- [tool.ruff]
61
- line-length = 120
62
- target-version = "py38"
63
-
64
- [tool.ruff.lint]
65
- extend-select = [
66
- "Q",
67
- "RUF100",
68
- "C90",
69
- "UP",
70
- "I",
71
- "D",
72
- "T",
73
- ]
74
- extend-ignore = []
75
-
76
- [tool.ruff.lint.flake8-quotes]
77
-
78
- [tool.ruff.lint.isort]
79
- known-first-party = []
80
-
81
- [tool.ruff.lint.mccabe]
82
- max-complexity = 14
83
-
84
- [tool.ruff.lint.pydocstyle]
85
- convention = "google"
86
-
87
- [tool.ruff.lint.per-file-ignores]
88
- "src/pandasql/__init__.py" = [
89
- "D",
90
- ]
91
- "examples/**/*.py" = [
92
- "D",
93
- ]
94
- "tests/**/*.py" = [
95
- "D",
96
- ]
97
-
98
- [tool.ruff.lint.extend-per-file-ignores]
99
- "examples/**/*.py" = [
100
- "T",
101
- ]
102
- "tests/**/*.py" = [
103
- "T",
104
- "E721",
105
- "F811",
106
- ]
107
-
108
- [tool.ruff.format]
109
- quote-style = "double"
@@ -1,333 +0,0 @@
1
- import importlib.metadata
2
- import string
3
- from typing import Any, Dict
4
-
5
- import packaging.version
6
- import pandas as pd
7
- import pandas.testing as pdtest
8
- import pytest
9
-
10
- from pandasql import PandaSQL, PandaSQLException, load_meat, sqldf
11
-
12
- PANDAS_MAJOR_VERSION = packaging.version.Version(
13
- importlib.metadata.version("pandas")
14
- ).major
15
-
16
-
17
- @pytest.fixture()
18
- def db_uris() -> Dict[str, str]:
19
- return {
20
- "sqlite": "sqlite:///:memory:",
21
- "postgres": "postgresql://postgres@localhost/",
22
- }
23
-
24
-
25
- @pytest.fixture(params=["sqlite", "postgres"])
26
- def db_flavor(request: pytest.FixtureRequest) -> Any:
27
- return request.param
28
-
29
-
30
- @pytest.fixture()
31
- def db_uri(db_uris: Dict[str, str], db_flavor: str) -> str:
32
- return db_uris[db_flavor]
33
-
34
-
35
- @pytest.fixture(params=[False, True])
36
- def pdsql(db_uri: str, request: pytest.FixtureRequest) -> PandaSQL:
37
- return PandaSQL(db_uri, persist=request.param)
38
-
39
-
40
- def test_select_legacy(db_uri: str) -> None:
41
- df = pd.DataFrame(
42
- {
43
- "letter_pos": range(len(string.ascii_letters)),
44
- "l2": list(string.ascii_letters),
45
- }
46
- )
47
- result = sqldf("SELECT * FROM df LIMIT 10", db_uri=db_uri)
48
-
49
- assert result is not None
50
- assert len(result) == 10
51
- pdtest.assert_frame_equal(df.head(10), result)
52
-
53
-
54
- def test_select(pdsql: PandaSQL) -> None:
55
- df = pd.DataFrame(
56
- {
57
- "letter_pos": range(len(string.ascii_letters)),
58
- "l2": list(string.ascii_letters),
59
- }
60
- )
61
- result = pdsql("SELECT * FROM df LIMIT 10")
62
-
63
- assert result is not None
64
- assert len(result) == 10
65
- pdtest.assert_frame_equal(df.head(10), result)
66
-
67
-
68
- def test_join(pdsql: PandaSQL) -> None:
69
- df = pd.DataFrame(
70
- {
71
- "letter_pos": range(len(string.ascii_letters)),
72
- "l2": list(string.ascii_letters),
73
- }
74
- )
75
-
76
- df2 = pd.DataFrame(
77
- {
78
- "letter_pos": range(len(string.ascii_letters)),
79
- "letter": list(string.ascii_letters),
80
- }
81
- )
82
-
83
- result = pdsql(
84
- "SELECT a.*, b.letter FROM df a INNER JOIN df2 b ON a.l2 = b.letter LIMIT 20"
85
- )
86
-
87
- assert result is not None
88
- assert len(result) == 20
89
- pdtest.assert_frame_equal(
90
- df[["letter_pos", "l2"]].head(20), result[["letter_pos", "l2"]]
91
- )
92
- pdtest.assert_frame_equal(df2[["letter"]].head(20), result[["letter"]])
93
-
94
-
95
- def test_query_with_spacing(pdsql: PandaSQL) -> None:
96
- df = pd.DataFrame(
97
- {
98
- "letter_pos": range(len(string.ascii_letters)),
99
- "l2": list(string.ascii_letters),
100
- }
101
- )
102
- assert df is not None
103
-
104
- df2 = pd.DataFrame(
105
- {
106
- "letter_pos": range(len(string.ascii_letters)),
107
- "letter": list(string.ascii_letters),
108
- }
109
- )
110
- assert df2 is not None
111
-
112
- expected = pdsql(
113
- "SELECT a.*, b.letter FROM df a INNER JOIN df2 b ON a.l2 = b.letter LIMIT 20"
114
- )
115
- assert expected is not None
116
-
117
- q = """
118
- SELECT
119
- a.*
120
- , b.letter
121
- FROM
122
- df a
123
- INNER JOIN
124
- df2 b
125
- on a.l2 = b.letter
126
- LIMIT 20
127
- """
128
- result = pdsql(q)
129
- assert result is not None
130
- assert len(result) == 20
131
- pdtest.assert_frame_equal(expected, result)
132
-
133
-
134
- def test_subquery(pdsql: PandaSQL) -> None:
135
- kermit = pd.DataFrame({"x": range(10)})
136
- result = pdsql("SELECT * FROM (SELECT * FROM kermit) tbl LIMIT 2")
137
- assert result is not None
138
- pdtest.assert_frame_equal(kermit.head(2), result)
139
- assert len(result) == 2
140
-
141
-
142
- def test_in(pdsql: PandaSQL) -> None:
143
- course_data = {
144
- "coursecode": ["TM351", "TU100", "M269"],
145
- "points": [30, 60, 30],
146
- "level": ["3", "1", "2"],
147
- }
148
- course_df = pd.DataFrame(course_data)
149
- assert not course_df.empty
150
-
151
- result = pdsql("SELECT * FROM course_df WHERE coursecode IN ( 'TM351', 'TU100' )")
152
- assert result is not None
153
- assert len(result) == 2
154
-
155
-
156
- def test_in_with_subquery(pdsql: PandaSQL) -> None:
157
- program_data = {
158
- "coursecode": [
159
- "TM351",
160
- "TM351",
161
- "TM351",
162
- "TU100",
163
- "TU100",
164
- "TU100",
165
- "M269",
166
- "M269",
167
- "M269",
168
- ],
169
- "programCode": ["AB1", "AB2", "AB3", "AB1", "AB3", "AB4", "AB3", "AB4", "AB5"],
170
- }
171
- program_df = pd.DataFrame(program_data)
172
- assert not program_df.empty
173
-
174
- courseData = {
175
- "coursecode": ["TM351", "TU100", "M269"],
176
- "points": [30, 60, 30],
177
- "level": ["3", "1", "2"],
178
- }
179
- course_df = pd.DataFrame(courseData)
180
- assert not course_df.empty
181
-
182
- result = pdsql(
183
- "SELECT * FROM course_df WHERE coursecode IN ( SELECT DISTINCT coursecode FROM program_df )"
184
- )
185
- assert result is not None
186
- assert len(result) == 3
187
-
188
-
189
- def test_datetime_query(pdsql: PandaSQL, db_flavor: str) -> None:
190
- meat = load_meat()
191
- expected = meat[meat["date"] >= "2012-01-01"].reset_index(drop=True)
192
- result = pdsql("SELECT * FROM meat WHERE date >= '2012-01-01'")
193
- assert result is not None
194
-
195
- if db_flavor == "sqlite":
196
- # sqlite uses strings instead of datetimes
197
- pdtest.assert_frame_equal(
198
- expected.drop(labels="date", axis="columns"),
199
- result.drop(labels="date", axis="columns"),
200
- )
201
- else:
202
- pdtest.assert_frame_equal(expected, result)
203
-
204
-
205
- def test_returning_single(pdsql: PandaSQL) -> None:
206
- meat = load_meat()
207
- result = pdsql("SELECT beef FROM meat LIMIT 10")
208
- assert result is not None
209
- assert len(result) == 10
210
- pdtest.assert_frame_equal(meat[["beef"]].head(10), result)
211
-
212
-
213
- def test_name_index(pdsql: PandaSQL) -> None:
214
- df = pd.DataFrame(
215
- {
216
- "index": range(len(string.ascii_letters)),
217
- "level_0": range(len(string.ascii_letters)),
218
- "level_1": range(len(string.ascii_letters)),
219
- "letter": list(string.ascii_letters),
220
- }
221
- )
222
- result = pdsql("SELECT * FROM df")
223
- assert result is not None
224
- pdtest.assert_frame_equal(df, result)
225
-
226
-
227
- def test_nonexistent_table(pdsql: PandaSQL) -> None:
228
- with pytest.raises(PandaSQLException):
229
- pdsql("SELECT * FROM nosuchtablereally")
230
-
231
-
232
- def test_system_tables(pdsql: PandaSQL, db_flavor: str) -> None:
233
- if db_flavor == "sqlite":
234
- # sqlite doesn't have information_schema
235
- result = pdsql("SELECT * FROM sqlite_master")
236
- else:
237
- result = pdsql("SELECT * FROM information_schema.tables")
238
- assert result is not None
239
- assert len(result.columns) > 1
240
-
241
-
242
- @pytest.mark.parametrize(
243
- "db_flavor", ["postgres"]
244
- ) # sqlite doesn't support tables with no columns
245
- def test_no_columns(pdsql: PandaSQL) -> None:
246
- df = pd.DataFrame()
247
- result = pdsql("SELECT * FROM df")
248
- assert result is not None
249
- assert df.empty and result.empty
250
- pdtest.assert_frame_equal(df, result, check_column_type=(PANDAS_MAJOR_VERSION < 2))
251
-
252
-
253
- def test_empty(pdsql: PandaSQL) -> None:
254
- df = pd.DataFrame({"x": []})
255
- result = pdsql("SELECT * FROM df")
256
- assert result is not None
257
- assert result.empty
258
- pdtest.assert_index_equal(df.columns, result.columns)
259
-
260
-
261
- def test_noleak_legacy(db_uri: str) -> None:
262
- df = pd.DataFrame({"x": [1]})
263
- result = sqldf("SELECT * FROM df", db_uri=db_uri)
264
- assert result is not None
265
- pdtest.assert_frame_equal(df, result)
266
- del df
267
- with pytest.raises(PandaSQLException):
268
- result = sqldf("SELECT * FROM df", db_uri=db_uri)
269
-
270
-
271
- @pytest.mark.parametrize("pdsql", [False], indirect=True)
272
- def test_noleak_class(pdsql: PandaSQL) -> None:
273
- df = pd.DataFrame({"x": [1]})
274
- result = pdsql("SELECT * FROM df")
275
- assert result is not None
276
- pdtest.assert_frame_equal(df, result)
277
- del df
278
- with pytest.raises(PandaSQLException):
279
- result = pdsql("SELECT * FROM df")
280
-
281
-
282
- def test_same_query_noerr(pdsql: PandaSQL) -> None:
283
- df = pd.DataFrame({"x": [1]})
284
- result1 = pdsql("SELECT * FROM df")
285
- assert result1 is not None
286
- pdtest.assert_frame_equal(df, result1)
287
- result2 = pdsql("SELECT * FROM df")
288
- assert result2 is not None
289
- pdtest.assert_frame_equal(result1, result2)
290
-
291
-
292
- @pytest.mark.parametrize("pdsql", [True], indirect=True)
293
- def test_persistent(pdsql: PandaSQL) -> None:
294
- df = pd.DataFrame({"x": [1]})
295
- result1 = pdsql("SELECT * FROM df")
296
- assert result1 is not None
297
- pdtest.assert_frame_equal(df, result1)
298
-
299
- del df
300
- result2 = pdsql("SELECT * FROM df")
301
- assert result2 is not None
302
- pdtest.assert_frame_equal(result1, result2)
303
-
304
- df = pd.DataFrame({"x": [1, 2]}) # will not have any effect
305
- assert not df.empty
306
- result3 = pdsql("SELECT * FROM df")
307
- assert result3 is not None
308
- pdtest.assert_frame_equal(result1, result3)
309
-
310
- df1 = pd.DataFrame({"x": [1, 2, 3]})
311
- result4 = pdsql("SELECT * FROM df1")
312
- assert result4 is not None
313
- pdtest.assert_frame_equal(df1, result4)
314
-
315
-
316
- def test_noreturn_query(pdsql: PandaSQL) -> None:
317
- assert pdsql("CREATE TABLE tbl (col INTEGER)") is None
318
-
319
-
320
- @pytest.mark.parametrize("pdsql", [False], indirect=True)
321
- def test_no_sideeffect_leak(pdsql: PandaSQL) -> None:
322
- pdsql("CREATE TABLE tbl (col INTEGER)")
323
- with pytest.raises(PandaSQLException):
324
- result = pdsql("SELECT * FROM tbl")
325
- assert result is not None
326
-
327
-
328
- @pytest.mark.parametrize("pdsql", [True], indirect=True)
329
- def test_sideeffect_persist(pdsql: PandaSQL) -> None:
330
- pdsql("CREATE TABLE tbl (col INTEGER)")
331
- result = pdsql("SELECT * FROM tbl")
332
- assert result is not None
333
- assert list(result.columns) == ["col"]
@@ -1,46 +0,0 @@
1
- from typing import Set
2
-
3
- import pytest
4
-
5
- from pandasql.sqldf import extract_table_names, get_outer_frame_variables
6
-
7
-
8
- def test_get_vars() -> None:
9
- var_a = 123
10
- variable = {"a": "b", "c": "d"}
11
- assert get_outer_frame_variables()["var_a"] == var_a
12
- assert get_outer_frame_variables()["variable"] == variable
13
-
14
-
15
- @pytest.mark.parametrize(
16
- ("query", "expected"),
17
- [
18
- ("", set()),
19
- ("SELECT * FROM dbtbl LIMIT 10", {"dbtbl"}),
20
- ("SELECT * from dbtbl LIMIT 10", {"dbtbl"}),
21
- (
22
- "SELECT * FROM dbtbl JOIN table_2 USING (a) LEFT JOIN mytable USING (b)",
23
- {"dbtbl", "table_2", "mytable"},
24
- ),
25
- (
26
- "SELECT * FROM dbtbl, table_2, mytable JOIN another USING (a)",
27
- {"dbtbl", "table_2", "mytable", "another"},
28
- ),
29
- ("SELECT * FROM dbtbl, table_2 JOIN dbtbl USING (a)", {"dbtbl", "table_2"}),
30
- pytest.param(
31
- "SELECT * FROM dbtbl WHERE col = 'Go and join us!'",
32
- {"dbtbl"},
33
- marks=pytest.mark.xfail,
34
- ),
35
- (
36
- "SELECT * FROM course_df WHERE coursecode IN ( SELECT DISTINCT coursecode FROM program_df )",
37
- {"course_df", "program_df"},
38
- ),
39
- (
40
- "SELECT * FROM course_df, (SELECT coursecode FROM program_df) ssel",
41
- {"course_df", "program_df"},
42
- ),
43
- ],
44
- )
45
- def test_extract_table_names(query: str, expected: Set[str]) -> None:
46
- assert extract_table_names(query) == expected