sqlqb 0.1.2__tar.gz → 0.1.3__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.
- {sqlqb-0.1.2 → sqlqb-0.1.3}/PKG-INFO +1 -1
- {sqlqb-0.1.2 → sqlqb-0.1.3}/pyproject.toml +7 -1
- {sqlqb-0.1.2/src/sqlqb/mysql → sqlqb-0.1.3/src/sqlqb/pymysql}/__init__.py +9 -6
- sqlqb-0.1.3/src/sqlqb/sqlalchemy/__init__.py +95 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/src/sqlqb/sqlite/__init__.py +13 -11
- sqlqb-0.1.3/tests/test_pymysql.py +223 -0
- sqlqb-0.1.3/tests/test_sqlalchemy.py +226 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/tests/test_sqlite.py +2 -7
- {sqlqb-0.1.2 → sqlqb-0.1.3}/uv.lock +123 -1
- {sqlqb-0.1.2 → sqlqb-0.1.3}/.gitignore +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/Makefile +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/README.md +0 -0
- /sqlqb-0.1.2/test.db → /sqlqb-0.1.3/data.db +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/src/sqlqb/__init__.py +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/src/sqlqb/delete.py +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/src/sqlqb/insert.py +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/src/sqlqb/select.py +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/src/sqlqb/update.py +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/tests/__init__.py +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/tests/test_delete.py +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/tests/test_insert.py +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/tests/test_select.py +0 -0
- {sqlqb-0.1.2 → sqlqb-0.1.3}/tests/test_update.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlqb"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
description = "A simple sql query builder"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = []
|
|
@@ -13,6 +13,12 @@ dev = [
|
|
|
13
13
|
"pytest-cov>=7.1.0",
|
|
14
14
|
"ruff>=0.15.17",
|
|
15
15
|
]
|
|
16
|
+
pymysql = [
|
|
17
|
+
"pymysql>=1.2.0",
|
|
18
|
+
]
|
|
19
|
+
sqlalchemy = [
|
|
20
|
+
"sqlalchemy>=2.0.51",
|
|
21
|
+
]
|
|
16
22
|
|
|
17
23
|
[build-system]
|
|
18
24
|
requires = ["hatchling", "hatch-vcs"]
|
|
@@ -8,7 +8,7 @@ from sqlqb import Delete as _Delete
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Select(_Select):
|
|
11
|
-
def __init__(self, connection, *args):
|
|
11
|
+
def __init__(self, connection: _Connection, *args):
|
|
12
12
|
super().__init__(*args)
|
|
13
13
|
self.__connection = connection
|
|
14
14
|
|
|
@@ -31,7 +31,7 @@ class Select(_Select):
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class Insert(_Insert):
|
|
34
|
-
def __init__(self, connection):
|
|
34
|
+
def __init__(self, connection: _Connection):
|
|
35
35
|
super().__init__()
|
|
36
36
|
self.__connection = connection
|
|
37
37
|
|
|
@@ -42,7 +42,7 @@ class Insert(_Insert):
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class Update(_Update):
|
|
45
|
-
def __init__(self, connection, table: str):
|
|
45
|
+
def __init__(self, connection: _Connection, table: str):
|
|
46
46
|
super().__init__(table)
|
|
47
47
|
self.__connection = connection
|
|
48
48
|
|
|
@@ -53,7 +53,7 @@ class Update(_Update):
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
class Delete(_Delete):
|
|
56
|
-
def __init__(self, connection):
|
|
56
|
+
def __init__(self, connection: _Connection):
|
|
57
57
|
super().__init__()
|
|
58
58
|
self.__connection = connection
|
|
59
59
|
|
|
@@ -75,8 +75,11 @@ class Connection(_Connection):
|
|
|
75
75
|
def Insert(self) -> Insert:
|
|
76
76
|
return Insert(self)
|
|
77
77
|
|
|
78
|
+
def Update(self, table: str) -> Update:
|
|
79
|
+
return Update(self, table)
|
|
80
|
+
|
|
78
81
|
def Delete(self) -> Delete:
|
|
79
82
|
return Delete(self)
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
|
|
85
|
+
connect = Connection
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from sqlalchemy import create_engine as _create_engine
|
|
2
|
+
from sqlalchemy import text
|
|
3
|
+
from sqlalchemy.engine import Connection as _Connection
|
|
4
|
+
from sqlalchemy.engine import Engine as _Engine
|
|
5
|
+
|
|
6
|
+
from sqlqb import Select as _Select
|
|
7
|
+
from sqlqb import Insert as _Insert
|
|
8
|
+
from sqlqb import Update as _Update
|
|
9
|
+
from sqlqb import Delete as _Delete
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _bind(sql: str, params: list) -> tuple[str, dict]:
|
|
13
|
+
"""Replace ? placeholders with :p0, :p1, ... for SQLAlchemy text()."""
|
|
14
|
+
named: dict = {}
|
|
15
|
+
for i, val in enumerate(params):
|
|
16
|
+
key = f"p{i}"
|
|
17
|
+
sql = sql.replace("?", f":{key}", 1)
|
|
18
|
+
named[key] = val
|
|
19
|
+
return sql, named
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Select(_Select):
|
|
23
|
+
def __init__(self, connection: _Connection, *args):
|
|
24
|
+
super().__init__(*args)
|
|
25
|
+
self.__connection = connection
|
|
26
|
+
|
|
27
|
+
def execute(self):
|
|
28
|
+
sql, params = _bind(self.sql, self.params)
|
|
29
|
+
return self.__connection.execute(text(sql), params)
|
|
30
|
+
|
|
31
|
+
def fetchone(self):
|
|
32
|
+
sql, params = _bind(self.sql, self.params)
|
|
33
|
+
row = self.execute().mappings().fetchone()
|
|
34
|
+
return dict(row) if row is not None else None
|
|
35
|
+
|
|
36
|
+
def fetchall(self) -> list:
|
|
37
|
+
sql, params = _bind(self.sql, self.params)
|
|
38
|
+
return [dict(row) for row in self.execute().mappings()]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Insert(_Insert):
|
|
42
|
+
def __init__(self, connection: _Connection):
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.__connection = connection
|
|
45
|
+
|
|
46
|
+
def execute(self) -> int:
|
|
47
|
+
sql, params = _bind(self.sql, self.params)
|
|
48
|
+
return self.__connection.execute(text(sql), params).rowcount
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Update(_Update):
|
|
52
|
+
def __init__(self, connection: _Connection, table: str):
|
|
53
|
+
super().__init__(table)
|
|
54
|
+
self.__connection = connection
|
|
55
|
+
|
|
56
|
+
def execute(self) -> int:
|
|
57
|
+
sql, params = _bind(self.sql, self.params)
|
|
58
|
+
return self.__connection.execute(text(sql), params).rowcount
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Delete(_Delete):
|
|
62
|
+
def __init__(self, connection: _Connection):
|
|
63
|
+
super().__init__()
|
|
64
|
+
self.__connection = connection
|
|
65
|
+
|
|
66
|
+
def execute(self) -> int:
|
|
67
|
+
sql, params = _bind(self.sql, self.params)
|
|
68
|
+
return self.__connection.execute(text(sql), params).rowcount
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Connection(_Connection):
|
|
72
|
+
def Select(self, *args) -> Select:
|
|
73
|
+
return Select(self, *args)
|
|
74
|
+
|
|
75
|
+
def Insert(self) -> Insert:
|
|
76
|
+
return Insert(self)
|
|
77
|
+
|
|
78
|
+
def Update(self, table: str) -> Update:
|
|
79
|
+
return Update(self, table)
|
|
80
|
+
|
|
81
|
+
def Delete(self) -> Delete:
|
|
82
|
+
return Delete(self)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Engine(_Engine):
|
|
86
|
+
def connect(self) -> Connection:
|
|
87
|
+
conn = super().connect()
|
|
88
|
+
conn.__class__ = Connection
|
|
89
|
+
return conn
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def create_engine(url: str, **kwargs) -> Engine:
|
|
93
|
+
engine = _create_engine(url, **kwargs)
|
|
94
|
+
engine.__class__ = Engine
|
|
95
|
+
return engine
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
from sqlite3 import Connection as _Connection
|
|
2
|
+
from sqlite3 import Cursor as _Cursor
|
|
3
|
+
from sqlite3 import connect as _connect
|
|
2
4
|
|
|
3
5
|
from sqlqb import Select as _Select
|
|
4
6
|
from sqlqb import Insert as _Insert
|
|
@@ -7,11 +9,11 @@ from sqlqb import Delete as _Delete
|
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class Select(_Select):
|
|
10
|
-
def __init__(self, connection, *args):
|
|
12
|
+
def __init__(self, connection: _Connection, *args):
|
|
11
13
|
super().__init__(*args)
|
|
12
14
|
self.__connection = connection
|
|
13
15
|
|
|
14
|
-
def execute(self) ->
|
|
16
|
+
def execute(self) -> _Cursor:
|
|
15
17
|
return self.__connection.execute(self.sql, self.params)
|
|
16
18
|
|
|
17
19
|
def fetchone(self):
|
|
@@ -22,7 +24,7 @@ class Select(_Select):
|
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class Insert(_Insert):
|
|
25
|
-
def __init__(self, connection):
|
|
27
|
+
def __init__(self, connection: _Connection):
|
|
26
28
|
super().__init__()
|
|
27
29
|
self.__connection = connection
|
|
28
30
|
|
|
@@ -31,7 +33,7 @@ class Insert(_Insert):
|
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
class Update(_Update):
|
|
34
|
-
def __init__(self, connection, table: str):
|
|
36
|
+
def __init__(self, connection: _Connection, table: str):
|
|
35
37
|
super().__init__(table)
|
|
36
38
|
self.__connection = connection
|
|
37
39
|
|
|
@@ -40,7 +42,7 @@ class Update(_Update):
|
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
class Delete(_Delete):
|
|
43
|
-
def __init__(self, connection):
|
|
45
|
+
def __init__(self, connection: _Connection):
|
|
44
46
|
super().__init__()
|
|
45
47
|
self.__connection = connection
|
|
46
48
|
|
|
@@ -48,7 +50,7 @@ class Delete(_Delete):
|
|
|
48
50
|
return self.__connection.execute(self.sql, self.params).rowcount
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
class Connection(
|
|
53
|
+
class Connection(_Connection):
|
|
52
54
|
def __init__(self, *args, **kwargs):
|
|
53
55
|
super().__init__(*args, **kwargs)
|
|
54
56
|
self.row_factory = lambda cursor, row: dict(zip([col[0] for col in cursor.description], row))
|
|
@@ -59,12 +61,12 @@ class Connection(sqlite3.Connection):
|
|
|
59
61
|
def Insert(self) -> Insert:
|
|
60
62
|
return Insert(self)
|
|
61
63
|
|
|
62
|
-
def Delete(self) -> Delete:
|
|
63
|
-
return Delete(self)
|
|
64
|
-
|
|
65
64
|
def Update(self, table: str) -> Update:
|
|
66
65
|
return Update(self, table)
|
|
67
66
|
|
|
67
|
+
def Delete(self) -> Delete:
|
|
68
|
+
return Delete(self)
|
|
69
|
+
|
|
68
70
|
|
|
69
71
|
def connect(database, **kwargs) -> Connection:
|
|
70
|
-
return
|
|
72
|
+
return _connect(database, factory=Connection, **kwargs)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
from sqlqb import pymysql
|
|
5
|
+
from sqlqb.pymysql import Select, Insert, Update, Delete, Connection
|
|
6
|
+
from pymysql.cursors import DictCursor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def mock_conn():
|
|
11
|
+
"""Mock pymysql connection whose cursor supports both direct use and context manager."""
|
|
12
|
+
conn = MagicMock()
|
|
13
|
+
cursor = MagicMock()
|
|
14
|
+
conn.cursor.return_value = cursor
|
|
15
|
+
cursor.__enter__.return_value = cursor
|
|
16
|
+
cursor.__exit__.return_value = False
|
|
17
|
+
return conn, cursor
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def connection():
|
|
22
|
+
"""pymysql.Connection instance with the pymysql parent __init__ patched out."""
|
|
23
|
+
with patch.object(pymysql._Connection, "__init__", return_value=None):
|
|
24
|
+
conn = pymysql.Connection.__new__(pymysql.Connection)
|
|
25
|
+
yield conn
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestSelect:
|
|
29
|
+
def test_execute_returns_cursor(self, mock_conn):
|
|
30
|
+
conn, cursor = mock_conn
|
|
31
|
+
result = Select(conn, "id", "name").From("users").execute()
|
|
32
|
+
assert result is cursor
|
|
33
|
+
|
|
34
|
+
def test_execute_calls_cursor_execute_with_sql_and_params(self, mock_conn):
|
|
35
|
+
conn, cursor = mock_conn
|
|
36
|
+
q = Select(conn, "name").From("users").Where("id = ?", 1)
|
|
37
|
+
q.execute()
|
|
38
|
+
cursor.execute.assert_called_once_with(q.sql, q.params)
|
|
39
|
+
|
|
40
|
+
def test_execute_does_not_use_context_manager(self, mock_conn):
|
|
41
|
+
conn, cursor = mock_conn
|
|
42
|
+
Select(conn, "id").From("users").execute()
|
|
43
|
+
cursor.__enter__.assert_not_called()
|
|
44
|
+
|
|
45
|
+
def test_fetchone_returns_result(self, mock_conn):
|
|
46
|
+
conn, cursor = mock_conn
|
|
47
|
+
cursor.fetchone.return_value = {"id": 1, "name": "Alice"}
|
|
48
|
+
result = Select(conn, "id", "name").From("users").Where("id = ?", 1).fetchone()
|
|
49
|
+
assert result == {"id": 1, "name": "Alice"}
|
|
50
|
+
|
|
51
|
+
def test_fetchone_returns_none_when_no_row(self, mock_conn):
|
|
52
|
+
conn, cursor = mock_conn
|
|
53
|
+
cursor.fetchone.return_value = None
|
|
54
|
+
result = Select(conn, "id").From("users").Where("id = ?", 999).fetchone()
|
|
55
|
+
assert result is None
|
|
56
|
+
|
|
57
|
+
def test_fetchone_calls_cursor_execute(self, mock_conn):
|
|
58
|
+
conn, cursor = mock_conn
|
|
59
|
+
q = Select(conn, "name").From("users").Where("id = ?", 1)
|
|
60
|
+
q.fetchone()
|
|
61
|
+
cursor.execute.assert_called_once_with(q.sql, q.params)
|
|
62
|
+
|
|
63
|
+
def test_fetchone_uses_context_manager(self, mock_conn):
|
|
64
|
+
conn, cursor = mock_conn
|
|
65
|
+
Select(conn, "name").From("users").fetchone()
|
|
66
|
+
cursor.__enter__.assert_called_once()
|
|
67
|
+
|
|
68
|
+
def test_fetchall_returns_result(self, mock_conn):
|
|
69
|
+
conn, cursor = mock_conn
|
|
70
|
+
cursor.fetchall.return_value = [{"id": 1}, {"id": 2}]
|
|
71
|
+
result = Select(conn, "id").From("users").fetchall()
|
|
72
|
+
assert result == [{"id": 1}, {"id": 2}]
|
|
73
|
+
|
|
74
|
+
def test_fetchall_returns_empty_list(self, mock_conn):
|
|
75
|
+
conn, cursor = mock_conn
|
|
76
|
+
cursor.fetchall.return_value = []
|
|
77
|
+
result = Select(conn, "id").From("users").fetchall()
|
|
78
|
+
assert result == []
|
|
79
|
+
|
|
80
|
+
def test_fetchall_calls_cursor_execute(self, mock_conn):
|
|
81
|
+
conn, cursor = mock_conn
|
|
82
|
+
q = Select(conn, "id").From("users")
|
|
83
|
+
q.fetchall()
|
|
84
|
+
cursor.execute.assert_called_once_with(q.sql, q.params)
|
|
85
|
+
|
|
86
|
+
def test_fetchall_uses_context_manager(self, mock_conn):
|
|
87
|
+
conn, cursor = mock_conn
|
|
88
|
+
Select(conn, "id").From("users").fetchall()
|
|
89
|
+
cursor.__enter__.assert_called_once()
|
|
90
|
+
|
|
91
|
+
def test_multiple_where_params_passed_correctly(self, mock_conn):
|
|
92
|
+
conn, cursor = mock_conn
|
|
93
|
+
q = Select(conn, "name").From("users").Where("age > ?", 18).Where("active = ?", True)
|
|
94
|
+
q.fetchall()
|
|
95
|
+
cursor.execute.assert_called_once_with(q.sql, [18, True])
|
|
96
|
+
|
|
97
|
+
def test_chaining_with_order_and_limit(self, mock_conn):
|
|
98
|
+
conn, cursor = mock_conn
|
|
99
|
+
cursor.fetchall.return_value = [{"name": "Alice"}]
|
|
100
|
+
result = Select(conn, "name").From("users").Where("age >= ?", 18).OrderBy("name").Limit(1).fetchall()
|
|
101
|
+
assert result == [{"name": "Alice"}]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestInsert:
|
|
105
|
+
def test_execute_returns_rowcount(self, mock_conn):
|
|
106
|
+
conn, cursor = mock_conn
|
|
107
|
+
cursor.rowcount = 1
|
|
108
|
+
count = Insert(conn).Into("users").Values(id=1, name="Alice").execute()
|
|
109
|
+
assert count == 1
|
|
110
|
+
|
|
111
|
+
def test_execute_calls_cursor_execute(self, mock_conn):
|
|
112
|
+
conn, cursor = mock_conn
|
|
113
|
+
q = Insert(conn).Into("users").Values(id=1, name="Alice")
|
|
114
|
+
q.execute()
|
|
115
|
+
cursor.execute.assert_called_once_with(q.sql, q.params)
|
|
116
|
+
|
|
117
|
+
def test_execute_uses_context_manager(self, mock_conn):
|
|
118
|
+
conn, cursor = mock_conn
|
|
119
|
+
Insert(conn).Into("users").Values(id=1, name="Alice").execute()
|
|
120
|
+
cursor.__enter__.assert_called_once()
|
|
121
|
+
|
|
122
|
+
def test_execute_multiple_rows_rowcount(self, mock_conn):
|
|
123
|
+
conn, cursor = mock_conn
|
|
124
|
+
cursor.rowcount = 2
|
|
125
|
+
count = Insert(conn).Into("users").Values(id=1, name="Alice").Values(id=2, name="Bob").execute()
|
|
126
|
+
assert count == 2
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TestUpdate:
|
|
130
|
+
def test_execute_returns_rowcount(self, mock_conn):
|
|
131
|
+
conn, cursor = mock_conn
|
|
132
|
+
cursor.rowcount = 1
|
|
133
|
+
count = Update(conn, "users").Set(name="Alice").Where("id = ?", 1).execute()
|
|
134
|
+
assert count == 1
|
|
135
|
+
|
|
136
|
+
def test_execute_calls_cursor_execute(self, mock_conn):
|
|
137
|
+
conn, cursor = mock_conn
|
|
138
|
+
q = Update(conn, "users").Set(name="Alice").Where("id = ?", 1)
|
|
139
|
+
q.execute()
|
|
140
|
+
cursor.execute.assert_called_once_with(q.sql, q.params)
|
|
141
|
+
|
|
142
|
+
def test_execute_uses_context_manager(self, mock_conn):
|
|
143
|
+
conn, cursor = mock_conn
|
|
144
|
+
Update(conn, "users").Set(name="Alice").execute()
|
|
145
|
+
cursor.__enter__.assert_called_once()
|
|
146
|
+
|
|
147
|
+
def test_execute_no_where_returns_affected_rowcount(self, mock_conn):
|
|
148
|
+
conn, cursor = mock_conn
|
|
149
|
+
cursor.rowcount = 5
|
|
150
|
+
count = Update(conn, "users").Set(active=False).execute()
|
|
151
|
+
assert count == 5
|
|
152
|
+
|
|
153
|
+
def test_execute_multiple_set_columns(self, mock_conn):
|
|
154
|
+
conn, cursor = mock_conn
|
|
155
|
+
cursor.rowcount = 1
|
|
156
|
+
q = Update(conn, "users").Set(name="Bob", age=20).Where("id = ?", 2)
|
|
157
|
+
count = q.execute()
|
|
158
|
+
cursor.execute.assert_called_once_with(q.sql, q.params)
|
|
159
|
+
assert count == 1
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestDelete:
|
|
163
|
+
def test_execute_returns_rowcount(self, mock_conn):
|
|
164
|
+
conn, cursor = mock_conn
|
|
165
|
+
cursor.rowcount = 1
|
|
166
|
+
count = Delete(conn).From("users").Where("id = ?", 1).execute()
|
|
167
|
+
assert count == 1
|
|
168
|
+
|
|
169
|
+
def test_execute_calls_cursor_execute(self, mock_conn):
|
|
170
|
+
conn, cursor = mock_conn
|
|
171
|
+
q = Delete(conn).From("users").Where("id = ?", 1)
|
|
172
|
+
q.execute()
|
|
173
|
+
cursor.execute.assert_called_once_with(q.sql, q.params)
|
|
174
|
+
|
|
175
|
+
def test_execute_uses_context_manager(self, mock_conn):
|
|
176
|
+
conn, cursor = mock_conn
|
|
177
|
+
Delete(conn).From("users").Where("id = ?", 1).execute()
|
|
178
|
+
cursor.__enter__.assert_called_once()
|
|
179
|
+
|
|
180
|
+
def test_execute_no_where_returns_all_rowcount(self, mock_conn):
|
|
181
|
+
conn, cursor = mock_conn
|
|
182
|
+
cursor.rowcount = 3
|
|
183
|
+
count = Delete(conn).From("users").execute()
|
|
184
|
+
assert count == 3
|
|
185
|
+
|
|
186
|
+
def test_execute_with_limit(self, mock_conn):
|
|
187
|
+
conn, cursor = mock_conn
|
|
188
|
+
cursor.rowcount = 2
|
|
189
|
+
q = Delete(conn).From("users").Limit(2)
|
|
190
|
+
count = q.execute()
|
|
191
|
+
cursor.execute.assert_called_once_with(q.sql, q.params)
|
|
192
|
+
assert count == 2
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class TestConnection:
|
|
196
|
+
def test_default_cursorclass_is_dictcursor(self):
|
|
197
|
+
with patch.object(pymysql._Connection, "__init__", return_value=None) as mock_init:
|
|
198
|
+
Connection(host="localhost", user="root", password="secret", db="mydb")
|
|
199
|
+
kwargs = mock_init.call_args[1]
|
|
200
|
+
assert kwargs["cursorclass"] is DictCursor
|
|
201
|
+
|
|
202
|
+
def test_custom_cursorclass_is_not_overridden(self):
|
|
203
|
+
custom_cursor = MagicMock()
|
|
204
|
+
with patch.object(pymysql._Connection, "__init__", return_value=None) as mock_init:
|
|
205
|
+
Connection(host="localhost", cursorclass=custom_cursor)
|
|
206
|
+
kwargs = mock_init.call_args[1]
|
|
207
|
+
assert kwargs["cursorclass"] is custom_cursor
|
|
208
|
+
|
|
209
|
+
def test_select_returns_select_instance(self, connection):
|
|
210
|
+
result = connection.Select("id", "name")
|
|
211
|
+
assert isinstance(result, Select)
|
|
212
|
+
|
|
213
|
+
def test_insert_returns_insert_instance(self, connection):
|
|
214
|
+
result = connection.Insert()
|
|
215
|
+
assert isinstance(result, Insert)
|
|
216
|
+
|
|
217
|
+
def test_delete_returns_delete_instance(self, connection):
|
|
218
|
+
result = connection.Delete()
|
|
219
|
+
assert isinstance(result, Delete)
|
|
220
|
+
|
|
221
|
+
def test_update_returns_update_instance(self, connection):
|
|
222
|
+
result = connection.Update("users")
|
|
223
|
+
assert isinstance(result, Update)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from sqlalchemy import text
|
|
3
|
+
from sqlalchemy.engine import Connection as _SAConnection, Engine as _SAEngine
|
|
4
|
+
from sqlalchemy.pool import StaticPool
|
|
5
|
+
|
|
6
|
+
from sqlqb.sqlalchemy import Connection, Engine, create_engine
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture(scope="module")
|
|
10
|
+
def engine():
|
|
11
|
+
e = create_engine(
|
|
12
|
+
"sqlite:///:memory:",
|
|
13
|
+
connect_args={"check_same_thread": False},
|
|
14
|
+
poolclass=StaticPool,
|
|
15
|
+
)
|
|
16
|
+
with e.connect() as conn:
|
|
17
|
+
conn.execute(text("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)"))
|
|
18
|
+
conn.Insert().Into("users").Values(id=1, name="Alice", age=30).execute()
|
|
19
|
+
conn.Insert().Into("users").Values(id=2, name="Bob", age=17).execute()
|
|
20
|
+
conn.Insert().Into("users").Values(id=3, name="Carol", age=25).execute()
|
|
21
|
+
conn.commit()
|
|
22
|
+
return e
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def conn(engine):
|
|
27
|
+
with engine.connect() as c:
|
|
28
|
+
yield c
|
|
29
|
+
c.rollback()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Engine
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TestEngine:
|
|
38
|
+
def test_create_engine_returns_engine_subclass(self):
|
|
39
|
+
e = create_engine("sqlite:///:memory:")
|
|
40
|
+
assert isinstance(e, Engine)
|
|
41
|
+
assert isinstance(e, _SAEngine)
|
|
42
|
+
|
|
43
|
+
def test_connect_returns_connection_subclass(self, engine):
|
|
44
|
+
with engine.connect() as conn:
|
|
45
|
+
assert isinstance(conn, Connection)
|
|
46
|
+
assert isinstance(conn, _SAConnection)
|
|
47
|
+
|
|
48
|
+
def test_connect_as_context_manager(self, engine):
|
|
49
|
+
with engine.connect() as conn:
|
|
50
|
+
assert conn is not None
|
|
51
|
+
|
|
52
|
+
def test_connect_without_context_manager(self, engine):
|
|
53
|
+
conn = engine.connect()
|
|
54
|
+
assert isinstance(conn, Connection)
|
|
55
|
+
conn.close()
|
|
56
|
+
|
|
57
|
+
def test_engine_dialect_accessible(self):
|
|
58
|
+
e = create_engine("sqlite:///:memory:")
|
|
59
|
+
assert e.dialect.name == "sqlite"
|
|
60
|
+
|
|
61
|
+
def test_engine_dispose(self):
|
|
62
|
+
e = create_engine("sqlite:///:memory:")
|
|
63
|
+
e.dispose()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Connection
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TestConnection:
|
|
72
|
+
def test_sa_methods_are_accessible(self, conn):
|
|
73
|
+
assert callable(conn.commit)
|
|
74
|
+
assert callable(conn.rollback)
|
|
75
|
+
assert callable(conn.close)
|
|
76
|
+
assert callable(conn.execute)
|
|
77
|
+
|
|
78
|
+
def test_select_returns_select_instance(self, conn):
|
|
79
|
+
from sqlqb.sqlalchemy import Select
|
|
80
|
+
|
|
81
|
+
assert isinstance(conn.Select("id"), Select)
|
|
82
|
+
|
|
83
|
+
def test_insert_returns_insert_instance(self, conn):
|
|
84
|
+
from sqlqb.sqlalchemy import Insert
|
|
85
|
+
|
|
86
|
+
assert isinstance(conn.Insert(), Insert)
|
|
87
|
+
|
|
88
|
+
def test_update_returns_update_instance(self, conn):
|
|
89
|
+
from sqlqb.sqlalchemy import Update
|
|
90
|
+
|
|
91
|
+
assert isinstance(conn.Update("users"), Update)
|
|
92
|
+
|
|
93
|
+
def test_delete_returns_delete_instance(self, conn):
|
|
94
|
+
from sqlqb.sqlalchemy import Delete
|
|
95
|
+
|
|
96
|
+
assert isinstance(conn.Delete(), Delete)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# Select
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestSelect:
|
|
105
|
+
def test_fetchall(self, conn):
|
|
106
|
+
rows = conn.Select("id", "name").From("users").fetchall()
|
|
107
|
+
assert rows == [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}, {"id": 3, "name": "Carol"}]
|
|
108
|
+
|
|
109
|
+
def test_fetchone(self, conn):
|
|
110
|
+
row = conn.Select("name").From("users").Where("id = ?", 1).fetchone()
|
|
111
|
+
assert row == {"name": "Alice"}
|
|
112
|
+
|
|
113
|
+
def test_fetchone_returns_none_when_no_row(self, conn):
|
|
114
|
+
row = conn.Select("name").From("users").Where("id = ?", 999).fetchone()
|
|
115
|
+
assert row is None
|
|
116
|
+
|
|
117
|
+
def test_fetchall_returns_empty_list(self, conn):
|
|
118
|
+
rows = conn.Select("name").From("users").Where("id = ?", 999).fetchall()
|
|
119
|
+
assert rows == []
|
|
120
|
+
|
|
121
|
+
def test_where_single_param(self, conn):
|
|
122
|
+
rows = conn.Select("name").From("users").Where("age >= ?", 18).fetchall()
|
|
123
|
+
assert rows == [{"name": "Alice"}, {"name": "Carol"}]
|
|
124
|
+
|
|
125
|
+
def test_where_multiple_params(self, conn):
|
|
126
|
+
rows = conn.Select("name").From("users").Where("age >= ?", 18).Where("age <= ?", 29).fetchall()
|
|
127
|
+
assert rows == [{"name": "Carol"}]
|
|
128
|
+
|
|
129
|
+
def test_order_by_asc(self, conn):
|
|
130
|
+
rows = conn.Select("name").From("users").OrderBy("name").fetchall()
|
|
131
|
+
assert rows == [{"name": "Alice"}, {"name": "Bob"}, {"name": "Carol"}]
|
|
132
|
+
|
|
133
|
+
def test_order_by_desc(self, conn):
|
|
134
|
+
rows = conn.Select("name").From("users").OrderBy("age", "DESC").fetchall()
|
|
135
|
+
assert rows == [{"name": "Alice"}, {"name": "Carol"}, {"name": "Bob"}]
|
|
136
|
+
|
|
137
|
+
def test_limit(self, conn):
|
|
138
|
+
rows = conn.Select("id").From("users").Limit(2).fetchall()
|
|
139
|
+
assert rows == [{"id": 1}, {"id": 2}]
|
|
140
|
+
|
|
141
|
+
def test_execute_returns_sa_result(self, conn):
|
|
142
|
+
result = conn.Select("id", "name").From("users").execute()
|
|
143
|
+
rows = [dict(r) for r in result.mappings()]
|
|
144
|
+
assert rows == [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}, {"id": 3, "name": "Carol"}]
|
|
145
|
+
|
|
146
|
+
def test_fetchall_returns_plain_dicts(self, conn):
|
|
147
|
+
rows = conn.Select("id").From("users").Limit(1).fetchall()
|
|
148
|
+
assert type(rows[0]) is dict
|
|
149
|
+
|
|
150
|
+
def test_fetchone_returns_plain_dict(self, conn):
|
|
151
|
+
row = conn.Select("id").From("users").Where("id = ?", 1).fetchone()
|
|
152
|
+
assert type(row) is dict
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# Insert
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class TestInsert:
|
|
161
|
+
def test_execute_returns_rowcount(self, conn):
|
|
162
|
+
count = conn.Insert().Into("users").Values(id=4, name="Dave", age=40).execute()
|
|
163
|
+
assert count == 1
|
|
164
|
+
|
|
165
|
+
def test_inserted_row_is_queryable(self, conn):
|
|
166
|
+
conn.Insert().Into("users").Values(id=4, name="Dave", age=40).execute()
|
|
167
|
+
row = conn.Select("name").From("users").Where("id = ?", 4).fetchone()
|
|
168
|
+
assert row == {"name": "Dave"}
|
|
169
|
+
|
|
170
|
+
def test_insert_multiple_rows(self, conn):
|
|
171
|
+
count = conn.Insert().Into("users").Values(id=4, name="Dave", age=40).Values(id=5, name="Eve", age=22).execute()
|
|
172
|
+
assert count == 2
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
# Update
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class TestUpdate:
|
|
181
|
+
def test_execute_returns_rowcount(self, conn):
|
|
182
|
+
count = conn.Update("users").Set(name="Alicia").Where("id = ?", 1).execute()
|
|
183
|
+
assert count == 1
|
|
184
|
+
|
|
185
|
+
def test_update_is_reflected(self, conn):
|
|
186
|
+
conn.Update("users").Set(name="Alicia").Where("id = ?", 1).execute()
|
|
187
|
+
row = conn.Select("name").From("users").Where("id = ?", 1).fetchone()
|
|
188
|
+
assert row == {"name": "Alicia"}
|
|
189
|
+
|
|
190
|
+
def test_update_multiple_columns(self, conn):
|
|
191
|
+
conn.Update("users").Set(name="Bobby", age=18).Where("id = ?", 2).execute()
|
|
192
|
+
row = conn.Select("name", "age").From("users").Where("id = ?", 2).fetchone()
|
|
193
|
+
assert row == {"name": "Bobby", "age": 18}
|
|
194
|
+
|
|
195
|
+
def test_update_no_where_affects_all(self, conn):
|
|
196
|
+
count = conn.Update("users").Set(age=0).execute()
|
|
197
|
+
assert count == 3
|
|
198
|
+
|
|
199
|
+
def test_update_affects_correct_rows_only(self, conn):
|
|
200
|
+
conn.Update("users").Set(age=99).Where("id = ?", 1).execute()
|
|
201
|
+
rows = conn.Select("id", "age").From("users").fetchall()
|
|
202
|
+
assert rows == [{"id": 1, "age": 99}, {"id": 2, "age": 17}, {"id": 3, "age": 25}]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
# Delete
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class TestDelete:
|
|
211
|
+
def test_execute_returns_rowcount(self, conn):
|
|
212
|
+
conn.Insert().Into("users").Values(id=4, name="Temp", age=1).execute()
|
|
213
|
+
count = conn.Delete().From("users").Where("id = ?", 4).execute()
|
|
214
|
+
assert count == 1
|
|
215
|
+
|
|
216
|
+
def test_deleted_row_is_gone(self, conn):
|
|
217
|
+
conn.Insert().Into("users").Values(id=4, name="Temp", age=1).execute()
|
|
218
|
+
conn.Delete().From("users").Where("id = ?", 4).execute()
|
|
219
|
+
row = conn.Select("*").From("users").Where("id = ?", 4).fetchone()
|
|
220
|
+
assert row is None
|
|
221
|
+
|
|
222
|
+
def test_delete_no_where_clears_table(self, conn):
|
|
223
|
+
count = conn.Delete().From("users").execute()
|
|
224
|
+
assert count == 3
|
|
225
|
+
rows = conn.Select("*").From("users").fetchall()
|
|
226
|
+
assert rows == []
|
|
@@ -30,13 +30,7 @@ class TestSelect:
|
|
|
30
30
|
assert cursor.fetchone() == {"id": 1, "name": "Alice", "age": 30}
|
|
31
31
|
|
|
32
32
|
def test_chaining(self, conn):
|
|
33
|
-
rows = (
|
|
34
|
-
conn.Select("name")
|
|
35
|
-
.From("users")
|
|
36
|
-
.Where("age >= ?", 18)
|
|
37
|
-
.OrderBy("name")
|
|
38
|
-
.fetchall()
|
|
39
|
-
)
|
|
33
|
+
rows = conn.Select("name").From("users").Where("age >= ?", 18).OrderBy("name").fetchall()
|
|
40
34
|
assert rows == [{"name": "Alice"}, {"name": "Carol"}]
|
|
41
35
|
|
|
42
36
|
def test_limit(self, conn):
|
|
@@ -108,5 +102,6 @@ class TestDelete:
|
|
|
108
102
|
def test_delete_with_limit_not_supported(self, conn):
|
|
109
103
|
# NOTE: sqlite library of python does not support LIMIT in delete queries
|
|
110
104
|
import sqlite3
|
|
105
|
+
|
|
111
106
|
with pytest.raises(sqlite3.OperationalError):
|
|
112
107
|
conn.Delete().From("users").Limit(2).execute()
|
|
@@ -95,6 +95,61 @@ wheels = [
|
|
|
95
95
|
{ url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" },
|
|
96
96
|
]
|
|
97
97
|
|
|
98
|
+
[[package]]
|
|
99
|
+
name = "greenlet"
|
|
100
|
+
version = "3.5.2"
|
|
101
|
+
source = { registry = "https://pypi.org/simple" }
|
|
102
|
+
sdist = { url = "https://files.pythonhosted.org/packages/dd/8b/befc3cb36965f397d87e86fb3b00e3ec0dc67c1ecb0986d7f54ee528f018/greenlet-3.5.2.tar.gz", hash = "sha256:c1b906220d83c140361cdd12eef970fb5881a168b98ee58a43786426173da14c", size = 199243, upload-time = "2026-06-17T20:19:01.317Z" }
|
|
103
|
+
wheels = [
|
|
104
|
+
{ url = "https://files.pythonhosted.org/packages/3f/7a/6bc2a7835731387ed303b9390ce68a116ab053df05450a59181239200454/greenlet-3.5.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:76dae33e97b52743a19210931ee3e78a88fe1438bc2fc4ee5e7512d289bfad4f", size = 288351, upload-time = "2026-06-17T17:36:17.019Z" },
|
|
105
|
+
{ url = "https://files.pythonhosted.org/packages/57/1b/bd98062fcef6d0e9d0873ab6f2d029772e6ea342972ae43275bd6177900f/greenlet-3.5.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30252d191d6959df1d040b559a38fc017139606c5ecc2ad00416557c0355d742", size = 604273, upload-time = "2026-06-17T18:07:20.296Z" },
|
|
106
|
+
{ url = "https://files.pythonhosted.org/packages/25/e6/fe392c522bf45d976abe7db2793f6ef4e87b053ebb869deeaae46aeb54da/greenlet-3.5.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1adc23c50f22b0f5979521909a8360ab4a3d3bef8b641ce633a04cf1b1c967ea", size = 616536, upload-time = "2026-06-17T18:29:43.205Z" },
|
|
107
|
+
{ url = "https://files.pythonhosted.org/packages/68/4a/399ff81fa93a19d6a9df394cef0355f082dbc19ad41aba9593cd0ad444e2/greenlet-3.5.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f052fff492c52fdfa99bd3b3c1389a53de37dae76a0562741417f0d018f02b3", size = 613749, upload-time = "2026-06-17T17:39:28.148Z" },
|
|
108
|
+
{ url = "https://files.pythonhosted.org/packages/a5/75/f519593f12ad43d08e28c03a95cfe2eeae011707dbc9dab0c4a263ce90f9/greenlet-3.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:120b77c2a18ebf629c3a7886f68c6d01e065654844ad468f15bb93ace66f2094", size = 1573725, upload-time = "2026-06-17T18:22:12.023Z" },
|
|
109
|
+
{ url = "https://files.pythonhosted.org/packages/f1/bc/bc1ea4b0754c6c51bbf9d94677b0b1f7fbda8cbb404e44a896854fc0a940/greenlet-3.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a850f6224088ef7dcc70f1a545cb6b3d119c35d6dca63b925b9f35da0635cdad", size = 1638132, upload-time = "2026-06-17T17:40:06.971Z" },
|
|
110
|
+
{ url = "https://files.pythonhosted.org/packages/36/c0/f0f5a34247df60de285f75f22e57f14027f4b3c43820981854b5b643ca6d/greenlet-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:89da99ee8345b458ea2f16831dad31c88ddcdec454b48704d569a0b8fb28f146", size = 239393, upload-time = "2026-06-17T17:33:47.09Z" },
|
|
111
|
+
{ url = "https://files.pythonhosted.org/packages/09/17/a8544e165445f30aea67a8d9cf2786d2bb0eb1b0e0d224b4d9bd80e2d587/greenlet-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:ca92411942154023c65851e6077d8ca0d00f19de5fa80bb2c6f196ff6c920ba9", size = 237723, upload-time = "2026-06-17T17:36:47.776Z" },
|
|
112
|
+
{ url = "https://files.pythonhosted.org/packages/d0/3c/bb37b9d40d65b0741a8b040ca5c307034d0a9822994dff5f825c88dd7a6b/greenlet-3.5.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:0629377725977252159de1ebd3c6e49c170a63856e585446797bb3d66d4d9c34", size = 287178, upload-time = "2026-06-17T17:35:25.132Z" },
|
|
113
|
+
{ url = "https://files.pythonhosted.org/packages/f0/a6/0c5902393f492f8ceb19d0b5cf139284e3a11b333a049739643b1036b6f8/greenlet-3.5.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2ddf9eddc617681108dd071b3feabf3f4a4cd64846254aec4d4ceda098b639a", size = 606900, upload-time = "2026-06-17T18:07:21.692Z" },
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/d8/7c/42899c31d4b87148ae4e3f87f63e13398824be6241f4dde42ded95768a34/greenlet-3.5.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f41feb9f2b59e2e61ac9bea4e344ddd9396bf3cacb2583f73a3595ed7df6f8e7", size = 619265, upload-time = "2026-06-17T18:29:44.837Z" },
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/d3/52/4ff8c98d3cfe62b4515f8584ae14510a58f35c549cc5292b78d9b7a40b70/greenlet-3.5.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09201fa698768db245920b00fdc86ee3e73540f01ca6db162be9632642e1a473", size = 616187, upload-time = "2026-06-17T17:39:29.473Z" },
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/c9/a6/269c8bf9aefc13361ce1088f0e392b154cb21005de7862e42b5d782b81fd/greenlet-3.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1759fa4f14c398508cf20dc8037de55cc23ae8bd14c185c2718257837195ca5", size = 1573778, upload-time = "2026-06-17T18:22:13.497Z" },
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/1f/9b/391d015cbc6323e81b14c02cf825fdca7e0049c9bb489bf4ac72883118ba/greenlet-3.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9318cdeb9abdbfdd8bc8464ee4a06dffde2c7846e1def138365a6240ab2c9a5", size = 1638092, upload-time = "2026-06-17T17:40:08.163Z" },
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/49/53/5b4df711f4356c62e85d9f819d87966d526d1cfb32bae49a8f7d6fc36ea4/greenlet-3.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:2c3b3311af72b3d3b03cc0f1ffd11f072e834be5d0444105cf715fc44434e39c", size = 239352, upload-time = "2026-06-17T17:38:51.593Z" },
|
|
119
|
+
{ url = "https://files.pythonhosted.org/packages/bb/b6/18efc3a329ec035c3f344b8f2b60356451950ddf9b7b64ff00023778a1dd/greenlet-3.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:f9bbd6216c45a563c2a61e478e038b439d9f248bde44f775ea37d339da643af4", size = 237635, upload-time = "2026-06-17T17:35:36.632Z" },
|
|
120
|
+
{ url = "https://files.pythonhosted.org/packages/c7/89/aaafc8e14de4ac882e02ccb963225329b0e8578aba4365e71eb678e45722/greenlet-3.5.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1c31219badba285858ba8ed117f403dea7fafee6bade9a1991875aae530c3ceb", size = 287676, upload-time = "2026-06-17T17:33:31.514Z" },
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/b8/fc/2308249206c12ac70de7b9a00970f84f07d10b3cd60e05d2fbcaa84124e8/greenlet-3.5.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f96ed6f4adc1066954ae95f45717657cb67468ef3b89e9a3632e14a625a8f39", size = 653552, upload-time = "2026-06-17T18:07:23.493Z" },
|
|
122
|
+
{ url = "https://files.pythonhosted.org/packages/7c/24/47730d1f8f1336b9b089237521ed7a26eee997065dcb4cab81cdca333abc/greenlet-3.5.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5795e883e915333c0d5648faaa691857fbc7180136883edc377f50f0d509c2a8", size = 665756, upload-time = "2026-06-17T18:29:46.616Z" },
|
|
123
|
+
{ url = "https://files.pythonhosted.org/packages/99/69/d6c99db15dc0b5e892ac3cc7b942c8b21f4a9cc3bd9ea0bc3b0f339ffbd4/greenlet-3.5.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26aed8d9503ca78889141a9739d71b383efea5f472a7c522b5410f7eb2a1b163", size = 663228, upload-time = "2026-06-17T17:39:31.073Z" },
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/4f/88/9e603f448e2bc107c883e95817b980fb9b45ba6aea0299b2e9978124bea2/greenlet-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dbebc038fcdda8f8f21cce985fd04e34e0f42007e7fc7ab7ad285caf77974b95", size = 1620723, upload-time = "2026-06-17T18:22:14.817Z" },
|
|
125
|
+
{ url = "https://files.pythonhosted.org/packages/11/91/26da17e3777858c16fdb8d020a4c68f3a03cb92f238de8f5351d5d5186e9/greenlet-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a207023f1cf8695fd82580b8099c09c5809be18bc2282362cdfb965dd884a317", size = 1684227, upload-time = "2026-06-17T17:40:09.536Z" },
|
|
126
|
+
{ url = "https://files.pythonhosted.org/packages/2d/44/b3a11f7aa34cb38f1b7f3df8bcd9fcd09bac9d342c2a2c9b8686c804bcd2/greenlet-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:c674a1dd4fe41f6a93febe7ab366ceabf15080ea31a9307811c56dac5f435f73", size = 240257, upload-time = "2026-06-17T17:35:23.359Z" },
|
|
127
|
+
{ url = "https://files.pythonhosted.org/packages/de/e3/3b62145fe917311732041a258adb218248add00542e3131c48bd047fbed5/greenlet-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3c417cd6c593bbbef6f7aa31a79f37d3db7d18832fc56b694a2150130bde784e", size = 239038, upload-time = "2026-06-17T17:37:56.792Z" },
|
|
128
|
+
{ url = "https://files.pythonhosted.org/packages/47/ac/d3bad483e9f6cd1848604fdffa32cac25846dd6dfcec0e6f81c790185518/greenlet-3.5.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a96457a30384de52d9c5d2fd33abf6c1daae3db392cd556738f408b1a79a1cf0", size = 295668, upload-time = "2026-06-17T17:36:02.293Z" },
|
|
129
|
+
{ url = "https://files.pythonhosted.org/packages/00/e9/3a7e557b895fd0469b00cd0b2bd498ba950e8bfdf6d7adeecf2c5e4130a6/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4af5d4961818ab651d09c1448a03b1ba2a1726a076266ebb62330bab9f3238c", size = 652820, upload-time = "2026-06-17T18:07:24.95Z" },
|
|
130
|
+
{ url = "https://files.pythonhosted.org/packages/78/67/6225d5c5e4afc04be0fd161eec82e4b72017e8a100d222f25d7b42b0140d/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a1789a6244ea1ba61fd4386c9a6a31873e9b0234762103364be98ef87dcb19f3", size = 658697, upload-time = "2026-06-17T18:29:48.365Z" },
|
|
131
|
+
{ url = "https://files.pythonhosted.org/packages/fa/99/6324b8ef916dcaddccb340b304c992ca3f947614ce0f2685d438187300b8/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3be00501fb4a8c37f6b4b3c4773808ceb26ea65c7ea64fd5735d0f330b3786de", size = 656436, upload-time = "2026-06-17T17:39:32.509Z" },
|
|
132
|
+
{ url = "https://files.pythonhosted.org/packages/a9/ee/f5bf9daac27c5e1b011965f64b5630a32b415daf7381b312943629e12c2a/greenlet-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1d554cd96841a68d464d75a3736f8e87408a7b02b1930a75fa32feb408ad62f8", size = 1617193, upload-time = "2026-06-17T18:22:16.252Z" },
|
|
133
|
+
{ url = "https://files.pythonhosted.org/packages/8a/21/b05d5b12715bda92ce27c118d64971d21e9b8f3563ed959a7d271e2d4223/greenlet-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3dff6cd3aac35f6cd3fc23460105acf576f5faf6c378de0bc088bf37c913864a", size = 1677512, upload-time = "2026-06-17T17:40:10.771Z" },
|
|
134
|
+
{ url = "https://files.pythonhosted.org/packages/b8/97/1b8f1314b868041b327dc1051603e8142b826480cb0ecb8a7b7632aee9c4/greenlet-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:36cfea2aa075d544617176b2e84450480f0797070ad8799a8c41ada2fe449d32", size = 243145, upload-time = "2026-06-17T17:34:37.502Z" },
|
|
135
|
+
{ url = "https://files.pythonhosted.org/packages/36/07/1b5311775e04c718a118c504d7a3a312430e2a1bd1347226aff4774e4549/greenlet-3.5.2-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:a0314aa832c94633355dc6f3ee54f195159533355a323f26926fc63b98b2ccbb", size = 288315, upload-time = "2026-06-17T17:34:34.04Z" },
|
|
136
|
+
{ url = "https://files.pythonhosted.org/packages/ed/cc/6abcd2a486b58b9f77b7a93b690d59cb2c11a5906ed2ad4c63c7b9c1113d/greenlet-3.5.2-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24c59cb7db9d5c694cb8fd0c76eef8e456b2123afdfa7e4b8f2a67a0860d7682", size = 659130, upload-time = "2026-06-17T18:07:26.354Z" },
|
|
137
|
+
{ url = "https://files.pythonhosted.org/packages/f2/12/f4aaad6d3d383233f700ab322568a4f29f2c701a4861d85f4811d99689b2/greenlet-3.5.2-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7bb811753703739ad318112f16eccfaabdac050037b6d092debaa8b23566b4ce", size = 669724, upload-time = "2026-06-17T18:29:50.13Z" },
|
|
138
|
+
{ url = "https://files.pythonhosted.org/packages/91/2a/a089811fc31c6bf8742f40a4e73470d6d401cef18e4314eb20dc399b377c/greenlet-3.5.2-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d78b5c1c178dad90447f1b8452262709d3eef4c98f825569e74c9d0b2260ac9", size = 668089, upload-time = "2026-06-17T17:39:33.808Z" },
|
|
139
|
+
{ url = "https://files.pythonhosted.org/packages/0f/1c/2f47c7d5fcfa98a62b705bf9a0505d86f4563c0d81cab1f7159ff1e743b7/greenlet-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:0977af2df83136f81c1f76e76d4e2fe7d0dc56ea9c101a86af26a95190b9ca32", size = 1625684, upload-time = "2026-06-17T18:22:17.664Z" },
|
|
140
|
+
{ url = "https://files.pythonhosted.org/packages/b9/bf/661dd24624f70b7b32972d7693d0344ecde10278f647d7b828baf739899c/greenlet-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f9ed777c6891d8253e54468576f55e27f8fc1a662a664f946a191003574c0a74", size = 1688043, upload-time = "2026-06-17T17:40:12.403Z" },
|
|
141
|
+
{ url = "https://files.pythonhosted.org/packages/60/49/d9bde1d15a21296b3b521fe083eb8aabd54ac05d15de9832918f3d639543/greenlet-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:c0ea4eb3de23f0bac1d75205e10ccfa9b418b17b01a2d7bf19e3b69dda08900a", size = 240531, upload-time = "2026-06-17T17:35:47.448Z" },
|
|
142
|
+
{ url = "https://files.pythonhosted.org/packages/7f/4d/86d7768bd53e9907de0333df215c2018cd01a593b3715cbd79aa82dd94b7/greenlet-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:7a7bfc200be40d04961d7e80e8337d726c0c1a50777e588123c3ed8ba731dcb9", size = 239579, upload-time = "2026-06-17T17:39:39.954Z" },
|
|
143
|
+
{ url = "https://files.pythonhosted.org/packages/92/15/907be5e8900901039bae752fa9a31c03a3c1e064833f35a4e49449184581/greenlet-3.5.2-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:98a52d6a50d4deaba304331d83ee3e10ebbdc1517fcca40b2715d1de4534065c", size = 296697, upload-time = "2026-06-17T17:37:15.887Z" },
|
|
144
|
+
{ url = "https://files.pythonhosted.org/packages/95/5c/08c57be575c3d6a3c023bbf22144a1c7dc6ed4d134527bb36ded4dbf04a8/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1587ff8b58fdf806993ed1490a06ac19c22d47b219c68b30954380029045d8d4", size = 656710, upload-time = "2026-06-17T18:07:28.046Z" },
|
|
145
|
+
{ url = "https://files.pythonhosted.org/packages/8c/d0/749f917bdc9fc90fceea4aa65fbf6556e617a50714d1496bdc8ad190bb36/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:feb721811d2754bfd16b48de151dd6b1f222c048e625151f2ca44cfdfd69f59c", size = 662629, upload-time = "2026-06-17T18:29:51.728Z" },
|
|
146
|
+
{ url = "https://files.pythonhosted.org/packages/5a/a5/68cefae3a07f6d0093a490cf28ab604f14578f3e60205a2a2b2d5cd70af2/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fe6062b1f35534e1e8fb28dfed406cf4eeff3e0bca3a0d9f8ff69f20a4abb00", size = 660147, upload-time = "2026-06-17T17:39:35.068Z" },
|
|
147
|
+
{ url = "https://files.pythonhosted.org/packages/d2/6b/b9156d8397e4750220f54c7c5c34650f1e740a8d2f66eab9cfd1b7b53b69/greenlet-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b4ac902af825cbac8e9b2fccab8122236fd2ba6c8b71a080116d2c2ec72671b1", size = 1621675, upload-time = "2026-06-17T18:22:18.873Z" },
|
|
148
|
+
{ url = "https://files.pythonhosted.org/packages/b0/e3/d3250f4fa01c211a93d04e34fded63187e648dbec17b9b1a14d388040593/greenlet-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6f1e473c06ae8be00c9034c2bb10fa277b08a93287e3111c395b839f01d27e1f", size = 1680577, upload-time = "2026-06-17T17:40:14.055Z" },
|
|
149
|
+
{ url = "https://files.pythonhosted.org/packages/55/ba/eaee8bda4419770d7096b5a009ebff0ab20a2a28cdd83c4b591bfdf36fa9/greenlet-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:3c2315045f9983e2e50d7e89d95405c21bddb8745f2da4487bc080ab3525f904", size = 243482, upload-time = "2026-06-17T17:37:34.741Z" },
|
|
150
|
+
{ url = "https://files.pythonhosted.org/packages/37/45/f794a81c91e9942c61f9110bd1f9a38a0ea565eab57f8b08cd53d3131e48/greenlet-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:db548d5ab6c2a8ead82c013f875090d79b5d7d2b67fc513934ce6cf66492ad7f", size = 242062, upload-time = "2026-06-17T17:35:39.814Z" },
|
|
151
|
+
]
|
|
152
|
+
|
|
98
153
|
[[package]]
|
|
99
154
|
name = "iniconfig"
|
|
100
155
|
version = "2.3.0"
|
|
@@ -131,6 +186,15 @@ wheels = [
|
|
|
131
186
|
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
|
132
187
|
]
|
|
133
188
|
|
|
189
|
+
[[package]]
|
|
190
|
+
name = "pymysql"
|
|
191
|
+
version = "1.2.0"
|
|
192
|
+
source = { registry = "https://pypi.org/simple" }
|
|
193
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c9/bc/1c6a92f385940f727daeecf3bacaf186e03875dff57197801046c583bcf0/pymysql-1.2.0.tar.gz", hash = "sha256:6c7b17ca686988104d7426c27895b455cdeea3e9d3ceb1270f0c3704fead8c33", size = 49021, upload-time = "2026-05-19T08:26:22.302Z" }
|
|
194
|
+
wheels = [
|
|
195
|
+
{ url = "https://files.pythonhosted.org/packages/c4/bd/2534e130295c8cfd4f0a2e31623baab7502278f1e97bcfe61db75656a77f/pymysql-1.2.0-py3-none-any.whl", hash = "sha256:62169ce6d5510f08e140c5e7990ee884a9764024e4a9a27b2cc11f1099322ae0", size = 45716, upload-time = "2026-05-19T08:26:20.974Z" },
|
|
196
|
+
]
|
|
197
|
+
|
|
134
198
|
[[package]]
|
|
135
199
|
name = "pytest"
|
|
136
200
|
version = "9.0.3"
|
|
@@ -186,9 +250,50 @@ wheels = [
|
|
|
186
250
|
{ url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" },
|
|
187
251
|
]
|
|
188
252
|
|
|
253
|
+
[[package]]
|
|
254
|
+
name = "sqlalchemy"
|
|
255
|
+
version = "2.0.51"
|
|
256
|
+
source = { registry = "https://pypi.org/simple" }
|
|
257
|
+
dependencies = [
|
|
258
|
+
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
|
|
259
|
+
{ name = "typing-extensions" },
|
|
260
|
+
]
|
|
261
|
+
sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" }
|
|
262
|
+
wheels = [
|
|
263
|
+
{ url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" },
|
|
264
|
+
{ url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" },
|
|
265
|
+
{ url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" },
|
|
266
|
+
{ url = "https://files.pythonhosted.org/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" },
|
|
267
|
+
{ url = "https://files.pythonhosted.org/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" },
|
|
268
|
+
{ url = "https://files.pythonhosted.org/packages/3c/b7/c5ffe50aa2f4d947c9250e1519d939260329a07fe6272edfccd784b3d007/sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5", size = 2119674, upload-time = "2026-06-15T16:23:09.543Z" },
|
|
269
|
+
{ url = "https://files.pythonhosted.org/packages/25/dc/46a65916af68a06ef6b972c6050ba4c8f97070fe3fb33097d34229d9bef6/sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080", size = 2146670, upload-time = "2026-06-15T16:23:11.048Z" },
|
|
270
|
+
{ url = "https://files.pythonhosted.org/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" },
|
|
271
|
+
{ url = "https://files.pythonhosted.org/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" },
|
|
272
|
+
{ url = "https://files.pythonhosted.org/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" },
|
|
273
|
+
{ url = "https://files.pythonhosted.org/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" },
|
|
274
|
+
{ url = "https://files.pythonhosted.org/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" },
|
|
275
|
+
{ url = "https://files.pythonhosted.org/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" },
|
|
276
|
+
{ url = "https://files.pythonhosted.org/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" },
|
|
277
|
+
{ url = "https://files.pythonhosted.org/packages/b1/49/a739be2e1d02a96a658eb71ab45d921c874249252358ad24a5bffdd02525/sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491", size = 2158999, upload-time = "2026-06-15T16:08:51.759Z" },
|
|
278
|
+
{ url = "https://files.pythonhosted.org/packages/23/6b/2e0e38cf75c8780eca78d9b2e78164f8bcfd70125e5caa588ff5cbb9c9f4/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d", size = 3282539, upload-time = "2026-06-15T16:19:51.065Z" },
|
|
279
|
+
{ url = "https://files.pythonhosted.org/packages/dd/a1/e77854cb5336fd37dc3c6ae3b71de242c98caac5725120be0b526b31cbd0/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54", size = 3287545, upload-time = "2026-06-15T16:26:44.735Z" },
|
|
280
|
+
{ url = "https://files.pythonhosted.org/packages/f6/ab/9e17272fd4dac8df3b83c4fbe52b998a1c9d89a843c8c35ff29b74ff7364/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e", size = 3230929, upload-time = "2026-06-15T16:19:52.625Z" },
|
|
281
|
+
{ url = "https://files.pythonhosted.org/packages/02/3c/52f408ea701781caee975606beccc48845f2aee8711ac29843d612c0306c/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d", size = 3252888, upload-time = "2026-06-15T16:26:46.454Z" },
|
|
282
|
+
{ url = "https://files.pythonhosted.org/packages/24/16/3efd2ee6bc4ca4693a30a1dd17a91b606cae15d517d2a4746611d9b73ce8/sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8", size = 2120551, upload-time = "2026-06-15T16:23:15.629Z" },
|
|
283
|
+
{ url = "https://files.pythonhosted.org/packages/7b/78/55b12e70f45bccc40d9e483925c065027b3b98ea4cbbdf6f8c2546feaf6c/sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499", size = 2146318, upload-time = "2026-06-15T16:23:17.108Z" },
|
|
284
|
+
{ url = "https://files.pythonhosted.org/packages/21/db/a9574ed40fed418924b1b1a3e54f47ee3963053b3d3d325a0d36b41f2c08/sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de", size = 2178920, upload-time = "2026-06-15T15:59:56.285Z" },
|
|
285
|
+
{ url = "https://files.pythonhosted.org/packages/bf/90/a1bb5c7cbba76b7bc1fbd586d0a5479a7bc9c27b4a8298f22ec9423b2bb3/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7", size = 3566534, upload-time = "2026-06-15T15:58:35.024Z" },
|
|
286
|
+
{ url = "https://files.pythonhosted.org/packages/15/4b/481f1fed30e0e9e8dd24aecbb49f29eb57fe7657ece5cf06ee9b84bb97d8/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72", size = 3535844, upload-time = "2026-06-15T16:02:43.973Z" },
|
|
287
|
+
{ url = "https://files.pythonhosted.org/packages/02/71/0aa64aeda645510af0a43f7d9ee70932f0d1dc4263aed34c50ee891d9df3/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23", size = 3475355, upload-time = "2026-06-15T15:58:36.592Z" },
|
|
288
|
+
{ url = "https://files.pythonhosted.org/packages/05/db/6061db32316446135a3abae5f308d144ab988a34234726042da3e58b1c63/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522", size = 3486591, upload-time = "2026-06-15T16:02:45.346Z" },
|
|
289
|
+
{ url = "https://files.pythonhosted.org/packages/0d/c9/f14fdf71bb8957e0c7e39db69bbdf12b5c80f4ef775fdfa127bf4e0d6760/sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7", size = 2151313, upload-time = "2026-06-15T16:03:39.127Z" },
|
|
290
|
+
{ url = "https://files.pythonhosted.org/packages/6a/c6/673e618e6f4f297e126d9b56ea2f6478708f6c1af4e3223835c22e2c3697/sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2", size = 2186280, upload-time = "2026-06-15T16:03:40.569Z" },
|
|
291
|
+
{ url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" },
|
|
292
|
+
]
|
|
293
|
+
|
|
189
294
|
[[package]]
|
|
190
295
|
name = "sqlqb"
|
|
191
|
-
version = "0.1.
|
|
296
|
+
version = "0.1.3"
|
|
192
297
|
source = { editable = "." }
|
|
193
298
|
|
|
194
299
|
[package.dev-dependencies]
|
|
@@ -197,6 +302,12 @@ dev = [
|
|
|
197
302
|
{ name = "pytest-cov" },
|
|
198
303
|
{ name = "ruff" },
|
|
199
304
|
]
|
|
305
|
+
pymysql = [
|
|
306
|
+
{ name = "pymysql" },
|
|
307
|
+
]
|
|
308
|
+
sqlalchemy = [
|
|
309
|
+
{ name = "sqlalchemy" },
|
|
310
|
+
]
|
|
200
311
|
|
|
201
312
|
[package.metadata]
|
|
202
313
|
|
|
@@ -206,3 +317,14 @@ dev = [
|
|
|
206
317
|
{ name = "pytest-cov", specifier = ">=7.1.0" },
|
|
207
318
|
{ name = "ruff", specifier = ">=0.15.17" },
|
|
208
319
|
]
|
|
320
|
+
pymysql = [{ name = "pymysql", specifier = ">=1.2.0" }]
|
|
321
|
+
sqlalchemy = [{ name = "sqlalchemy", specifier = ">=2.0.51" }]
|
|
322
|
+
|
|
323
|
+
[[package]]
|
|
324
|
+
name = "typing-extensions"
|
|
325
|
+
version = "4.15.0"
|
|
326
|
+
source = { registry = "https://pypi.org/simple" }
|
|
327
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
|
328
|
+
wheels = [
|
|
329
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
330
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|