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.
- {pandas_query_sql-0.8.0 → pandas_query_sql-0.8.2}/PKG-INFO +11 -14
- pandas_query_sql-0.8.2/pyproject.toml +74 -0
- {pandas_query_sql-0.8.0 → pandas_query_sql-0.8.2}/src/pandasql/__init__.py +4 -4
- {pandas_query_sql-0.8.0 → pandas_query_sql-0.8.2}/src/pandasql/sqldf.py +50 -34
- pandas_query_sql-0.8.0/AUTHORS.md +0 -11
- pandas_query_sql-0.8.0/pyproject.toml +0 -109
- pandas_query_sql-0.8.0/tests/pandasql/test_pandasql.py +0 -333
- pandas_query_sql-0.8.0/tests/pandasql/test_utils.py +0 -46
- {pandas_query_sql-0.8.0 → pandas_query_sql-0.8.2}/LICENSE.txt +0 -0
- {pandas_query_sql-0.8.0 → pandas_query_sql-0.8.2}/README.md +0 -0
- {pandas_query_sql-0.8.0 → pandas_query_sql-0.8.2}/src/pandasql/data/births.csv +0 -0
- {pandas_query_sql-0.8.0 → pandas_query_sql-0.8.2}/src/pandasql/data/births_by_month.csv +0 -0
- {pandas_query_sql-0.8.0 → pandas_query_sql-0.8.2}/src/pandasql/data/meat.csv +0 -0
- /pandas_query_sql-0.8.0/tests/__init__.py → /pandas_query_sql-0.8.2/src/pandasql/py.typed +0 -0
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pandas_query_sql
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: sqldf for pandas
|
|
5
|
-
Author
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-
|
|
17
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
9
|
+
_ROOT = pathlib.Path(__file__).absolute().parent
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def get_data(path: str) ->
|
|
13
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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,
|
|
71
|
-
|
|
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(
|
|
133
|
+
conn.execute( # pyright: ignore[reportUnknownMemberType]
|
|
134
|
+
statement=text("set search_path to pg_temp"),
|
|
135
|
+
)
|
|
129
136
|
else:
|
|
130
|
-
conn.execute(
|
|
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,
|
|
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() ->
|
|
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:
|
|
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) ->
|
|
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+)*)",
|
|
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,
|
|
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(
|
|
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,
|
|
193
|
-
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|