sqlqb 0.1.1__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.
Files changed (40) hide show
  1. {sqlqb-0.1.1 → sqlqb-0.1.3}/PKG-INFO +1 -1
  2. {sqlqb-0.1.1 → sqlqb-0.1.3}/pyproject.toml +7 -1
  3. sqlqb-0.1.3/src/sqlqb/__init__.py +6 -0
  4. sqlqb-0.1.3/src/sqlqb/delete.py +45 -0
  5. sqlqb-0.1.3/src/sqlqb/insert.py +45 -0
  6. sqlqb-0.1.3/src/sqlqb/pymysql/__init__.py +85 -0
  7. {sqlqb-0.1.1 → sqlqb-0.1.3}/src/sqlqb/select.py +19 -13
  8. sqlqb-0.1.3/src/sqlqb/sqlalchemy/__init__.py +95 -0
  9. sqlqb-0.1.3/src/sqlqb/sqlite/__init__.py +72 -0
  10. sqlqb-0.1.3/src/sqlqb/update.py +52 -0
  11. sqlqb-0.1.3/tests/test_delete.py +81 -0
  12. sqlqb-0.1.3/tests/test_insert.py +60 -0
  13. sqlqb-0.1.3/tests/test_pymysql.py +223 -0
  14. {sqlqb-0.1.1 → sqlqb-0.1.3}/tests/test_select.py +28 -10
  15. sqlqb-0.1.3/tests/test_sqlalchemy.py +226 -0
  16. sqlqb-0.1.3/tests/test_sqlite.py +107 -0
  17. sqlqb-0.1.3/tests/test_update.py +100 -0
  18. {sqlqb-0.1.1 → sqlqb-0.1.3}/uv.lock +123 -1
  19. sqlqb-0.1.1/htmlcov/.gitignore +0 -2
  20. sqlqb-0.1.1/htmlcov/class_index.html +0 -201
  21. sqlqb-0.1.1/htmlcov/coverage_html_cb_dd2e7eb5.js +0 -735
  22. sqlqb-0.1.1/htmlcov/favicon_32_cb_c827f16f.png +0 -0
  23. sqlqb-0.1.1/htmlcov/function_index.html +0 -391
  24. sqlqb-0.1.1/htmlcov/index.html +0 -144
  25. sqlqb-0.1.1/htmlcov/keybd_closed_cb_900cfef5.png +0 -0
  26. sqlqb-0.1.1/htmlcov/status.json +0 -1
  27. sqlqb-0.1.1/htmlcov/style_cb_4667309f.css +0 -391
  28. sqlqb-0.1.1/htmlcov/z_6088b58928f3e76c___init___py.html +0 -131
  29. sqlqb-0.1.1/htmlcov/z_96458094e7923210___init___py.html +0 -128
  30. sqlqb-0.1.1/htmlcov/z_c812cfdabd36609f___init___py.html +0 -100
  31. sqlqb-0.1.1/htmlcov/z_c812cfdabd36609f_select_py.html +0 -177
  32. sqlqb-0.1.1/src/sqlqb/__init__.py +0 -3
  33. sqlqb-0.1.1/src/sqlqb/mysql/__init__.py +0 -34
  34. sqlqb-0.1.1/src/sqlqb/sqlite/__init__.py +0 -31
  35. sqlqb-0.1.1/tests/test_sqlite.py +0 -52
  36. {sqlqb-0.1.1 → sqlqb-0.1.3}/.gitignore +0 -0
  37. {sqlqb-0.1.1 → sqlqb-0.1.3}/Makefile +0 -0
  38. {sqlqb-0.1.1 → sqlqb-0.1.3}/README.md +0 -0
  39. /sqlqb-0.1.1/test.db → /sqlqb-0.1.3/data.db +0 -0
  40. {sqlqb-0.1.1 → sqlqb-0.1.3}/tests/__init__.py +0 -0
@@ -1,5 +1,5 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlqb
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: A simple sql query builder
5
5
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlqb"
3
- version = "0.1.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"]
@@ -0,0 +1,6 @@
1
+ from .select import Select
2
+ from .insert import Insert
3
+ from .delete import Delete
4
+ from .update import Update
5
+
6
+ __all__ = ["Select", "Insert", "Delete", "Update"]
@@ -0,0 +1,45 @@
1
+ import io
2
+
3
+
4
+ class Delete:
5
+ def __init__(self):
6
+ self.__table: str | None = None
7
+ self.__wheres: list[str] = []
8
+ self.__params: list = []
9
+ self.__limit: int | None = None
10
+
11
+ def From(self, table: str) -> "Delete":
12
+ if not table or not table.strip():
13
+ raise ValueError("error: table name must not be empty")
14
+ self.__table = table
15
+ return self
16
+
17
+ def Where(self, condition: str, *args) -> "Delete":
18
+ self.__wheres.append(condition)
19
+ self.__params += args
20
+ return self
21
+
22
+ def Limit(self, limit: int) -> "Delete":
23
+ self.__limit = limit
24
+ return self
25
+
26
+ @property
27
+ def params(self) -> list:
28
+ return self.__params
29
+
30
+ @property
31
+ def sql(self) -> str:
32
+ if not self.__table:
33
+ raise ValueError("error: no table specified")
34
+ sql = io.StringIO()
35
+ sql.write("DELETE FROM ")
36
+ sql.write(self.__table)
37
+ if self.__wheres:
38
+ sql.write(" WHERE ")
39
+ sql.write(" AND ".join(self.__wheres))
40
+ if self.__limit is not None:
41
+ sql.write(f" LIMIT {self.__limit}")
42
+ return sql.getvalue()
43
+
44
+ def __str__(self) -> str:
45
+ return self.sql
@@ -0,0 +1,45 @@
1
+ import io
2
+
3
+
4
+ class Insert:
5
+ def __init__(self):
6
+ self.__table: str | None = None
7
+ self.__rows: list[dict] = []
8
+
9
+ def Into(self, table: str) -> "Insert":
10
+ if not table or not table.strip():
11
+ raise ValueError("error: table name must not be empty")
12
+ self.__table = table
13
+ return self
14
+
15
+ def Values(self, **kwargs) -> "Insert":
16
+ if not kwargs:
17
+ raise ValueError("error: specify at least one column value")
18
+ self.__rows.append(kwargs)
19
+ return self
20
+
21
+ @property
22
+ def params(self) -> list:
23
+ if not self.__rows:
24
+ return []
25
+ return [v for row in self.__rows for v in row.values()]
26
+
27
+ @property
28
+ def sql(self) -> str:
29
+ if not self.__table:
30
+ raise ValueError("error: no table specified")
31
+ if not self.__rows:
32
+ raise ValueError("error: no values specified")
33
+ columns = list(self.__rows[0].keys())
34
+ sql = io.StringIO()
35
+ sql.write("INSERT INTO ")
36
+ sql.write(self.__table)
37
+ sql.write(" (")
38
+ sql.write(", ".join(columns))
39
+ sql.write(") VALUES ")
40
+ placeholder = "(" + ", ".join(["?"] * len(columns)) + ")"
41
+ sql.write(", ".join([placeholder] * len(self.__rows)))
42
+ return sql.getvalue()
43
+
44
+ def __str__(self) -> str:
45
+ return self.sql
@@ -0,0 +1,85 @@
1
+ from pymysql.connections import Connection as _Connection
2
+ from pymysql.cursors import DictCursor
3
+
4
+ from sqlqb import Select as _Select
5
+ from sqlqb import Insert as _Insert
6
+ from sqlqb import Update as _Update
7
+ from sqlqb import Delete as _Delete
8
+
9
+
10
+ class Select(_Select):
11
+ def __init__(self, connection: _Connection, *args):
12
+ super().__init__(*args)
13
+ self.__connection = connection
14
+
15
+ def execute(self):
16
+ cursor = self.__connection.cursor()
17
+ cursor.execute(self.sql, self.params)
18
+ return cursor
19
+
20
+ def fetchone(self):
21
+ with self.__connection.cursor() as cursor:
22
+ cursor.execute(self.sql, self.params)
23
+ result = cursor.fetchone()
24
+ return result
25
+
26
+ def fetchall(self):
27
+ with self.__connection.cursor() as cursor:
28
+ cursor.execute(self.sql, self.params)
29
+ result = cursor.fetchall()
30
+ return result
31
+
32
+
33
+ class Insert(_Insert):
34
+ def __init__(self, connection: _Connection):
35
+ super().__init__()
36
+ self.__connection = connection
37
+
38
+ def execute(self) -> int:
39
+ with self.__connection.cursor() as cursor:
40
+ cursor.execute(self.sql, self.params)
41
+ return cursor.rowcount
42
+
43
+
44
+ class Update(_Update):
45
+ def __init__(self, connection: _Connection, table: str):
46
+ super().__init__(table)
47
+ self.__connection = connection
48
+
49
+ def execute(self) -> int:
50
+ with self.__connection.cursor() as cursor:
51
+ cursor.execute(self.sql, self.params)
52
+ return cursor.rowcount
53
+
54
+
55
+ class Delete(_Delete):
56
+ def __init__(self, connection: _Connection):
57
+ super().__init__()
58
+ self.__connection = connection
59
+
60
+ def execute(self) -> int:
61
+ with self.__connection.cursor() as cursor:
62
+ cursor.execute(self.sql, self.params)
63
+ return cursor.rowcount
64
+
65
+
66
+ class Connection(_Connection):
67
+ def __init__(self, *args, **kwargs):
68
+ if "cursorclass" not in kwargs:
69
+ kwargs["cursorclass"] = DictCursor
70
+ super().__init__(*args, **kwargs)
71
+
72
+ def Select(self, *args, **kwargs) -> Select:
73
+ return Select(self, *args, **kwargs)
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
+ connect = Connection
@@ -2,22 +2,26 @@ import io
2
2
 
3
3
 
4
4
  class Select:
5
- def __init__(self, *args):
6
- self.__columns = list(args)
7
- self.__table = None
8
- self.__wheres = []
9
- self.__params = []
10
- self.__joins = []
11
- self.__orderby = []
12
- self.__groupby = []
13
- self.__limit = None
14
- self.__offset = None
5
+ def __init__(self, *args: str):
6
+ self.__columns: list[str] = list(args)
7
+ self.__table: str | None = None
8
+ self.__wheres: list[str] = []
9
+ self.__params: list = []
10
+ self.__joins: list[str] = []
11
+ self.__orderby: list[tuple[str, str]] = []
12
+ self.__groupby: list[str] = []
13
+ self.__limit: int | None = None
14
+ self.__offset: int | None = None
15
15
 
16
- def Columns(self, *columns) -> "Select":
16
+ def Columns(self, *columns: str) -> "Select":
17
+ if not columns:
18
+ raise ValueError("error: specify at least one column")
17
19
  self.__columns = list(columns)
18
20
  return self
19
21
 
20
22
  def From(self, table: str) -> "Select":
23
+ if not table or not table.strip():
24
+ raise ValueError("error: table name must not be empty")
21
25
  self.__table = table
22
26
  return self
23
27
 
@@ -31,11 +35,13 @@ class Select:
31
35
  self.__params += args
32
36
  return self
33
37
 
34
- def OrderBy(self, column: str, direction="ASC") -> "Select":
38
+ def OrderBy(self, column: str, direction: str = "ASC") -> "Select":
39
+ if direction not in ("ASC", "DESC"):
40
+ raise ValueError("error: invalid order by direction")
35
41
  self.__orderby.append((column, direction))
36
42
  return self
37
43
 
38
- def GroupBy(self, *columns) -> "Select":
44
+ def GroupBy(self, *columns: str) -> "Select":
39
45
  self.__groupby += columns
40
46
  return self
41
47
 
@@ -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
@@ -0,0 +1,72 @@
1
+ from sqlite3 import Connection as _Connection
2
+ from sqlite3 import Cursor as _Cursor
3
+ from sqlite3 import connect as _connect
4
+
5
+ from sqlqb import Select as _Select
6
+ from sqlqb import Insert as _Insert
7
+ from sqlqb import Update as _Update
8
+ from sqlqb import Delete as _Delete
9
+
10
+
11
+ class Select(_Select):
12
+ def __init__(self, connection: _Connection, *args):
13
+ super().__init__(*args)
14
+ self.__connection = connection
15
+
16
+ def execute(self) -> _Cursor:
17
+ return self.__connection.execute(self.sql, self.params)
18
+
19
+ def fetchone(self):
20
+ return self.execute().fetchone()
21
+
22
+ def fetchall(self) -> list:
23
+ return self.execute().fetchall()
24
+
25
+
26
+ class Insert(_Insert):
27
+ def __init__(self, connection: _Connection):
28
+ super().__init__()
29
+ self.__connection = connection
30
+
31
+ def execute(self) -> int:
32
+ return self.__connection.execute(self.sql, self.params).rowcount
33
+
34
+
35
+ class Update(_Update):
36
+ def __init__(self, connection: _Connection, table: str):
37
+ super().__init__(table)
38
+ self.__connection = connection
39
+
40
+ def execute(self) -> int:
41
+ return self.__connection.execute(self.sql, self.params).rowcount
42
+
43
+
44
+ class Delete(_Delete):
45
+ def __init__(self, connection: _Connection):
46
+ super().__init__()
47
+ self.__connection = connection
48
+
49
+ def execute(self) -> int:
50
+ return self.__connection.execute(self.sql, self.params).rowcount
51
+
52
+
53
+ class Connection(_Connection):
54
+ def __init__(self, *args, **kwargs):
55
+ super().__init__(*args, **kwargs)
56
+ self.row_factory = lambda cursor, row: dict(zip([col[0] for col in cursor.description], row))
57
+
58
+ def Select(self, *args, **kwargs) -> Select:
59
+ return Select(self, *args, **kwargs)
60
+
61
+ def Insert(self) -> Insert:
62
+ return Insert(self)
63
+
64
+ def Update(self, table: str) -> Update:
65
+ return Update(self, table)
66
+
67
+ def Delete(self) -> Delete:
68
+ return Delete(self)
69
+
70
+
71
+ def connect(database, **kwargs) -> Connection:
72
+ return _connect(database, factory=Connection, **kwargs)
@@ -0,0 +1,52 @@
1
+ import io
2
+
3
+
4
+ class Update:
5
+ def __init__(self, table: str):
6
+ if not table or not table.strip():
7
+ raise ValueError("error: table name must not be empty")
8
+ self.__table = table
9
+ self.__sets: dict[str, object] = {}
10
+ self.__wheres: list[str] = []
11
+ self.__where_params: list = []
12
+ self.__limit: int | None = None
13
+
14
+ def Set(self, **kwargs) -> "Update":
15
+ if not kwargs:
16
+ raise ValueError("error: specify at least one column value")
17
+ self.__sets.update(kwargs)
18
+ return self
19
+
20
+ def Where(self, condition: str, *args) -> "Update":
21
+ self.__wheres.append(condition)
22
+ self.__where_params += args
23
+ return self
24
+
25
+ def Limit(self, limit: int) -> "Update":
26
+ self.__limit = limit
27
+ return self
28
+
29
+ @property
30
+ def params(self) -> list:
31
+ if not self.__sets:
32
+ return []
33
+ return list(self.__sets.values()) + self.__where_params
34
+
35
+ @property
36
+ def sql(self) -> str:
37
+ if not self.__sets:
38
+ raise ValueError("error: no SET values specified")
39
+ sql = io.StringIO()
40
+ sql.write("UPDATE ")
41
+ sql.write(self.__table)
42
+ sql.write(" SET ")
43
+ sql.write(", ".join([f"{col} = ?" for col in self.__sets]))
44
+ if self.__wheres:
45
+ sql.write(" WHERE ")
46
+ sql.write(" AND ".join(self.__wheres))
47
+ if self.__limit is not None:
48
+ sql.write(f" LIMIT {self.__limit}")
49
+ return sql.getvalue()
50
+
51
+ def __str__(self) -> str:
52
+ return self.sql
@@ -0,0 +1,81 @@
1
+ import pytest
2
+
3
+ from sqlqb import Delete
4
+
5
+
6
+ class TestDeleteBasic:
7
+ def test_delete_all(self):
8
+ q = Delete().From("users")
9
+ assert q.sql == "DELETE FROM users"
10
+
11
+ def test_str_equals_sql(self):
12
+ q = Delete().From("users")
13
+ assert str(q) == q.sql
14
+
15
+ def test_no_table_raises(self):
16
+ with pytest.raises(ValueError):
17
+ Delete().sql
18
+
19
+ def test_empty_table_raises(self):
20
+ with pytest.raises(ValueError):
21
+ Delete().From("")
22
+
23
+ def test_blank_table_raises(self):
24
+ with pytest.raises(ValueError):
25
+ Delete().From(" ")
26
+
27
+ def test_params_empty_by_default(self):
28
+ q = Delete().From("users")
29
+ assert q.params == []
30
+
31
+
32
+ class TestDeleteWhere:
33
+ def test_single_where(self):
34
+ q = Delete().From("users").Where("id = ?", 1)
35
+ assert q.sql == "DELETE FROM users WHERE id = ?"
36
+ assert q.params == [1]
37
+
38
+ def test_multiple_wheres_joined_with_and(self):
39
+ q = Delete().From("users").Where("active = ?", False).Where("role = ?", "guest")
40
+ assert q.sql == "DELETE FROM users WHERE active = ? AND role = ?"
41
+ assert q.params == [False, "guest"]
42
+
43
+ def test_where_no_params(self):
44
+ q = Delete().From("sessions").Where("expired_at IS NOT NULL")
45
+ assert q.sql == "DELETE FROM sessions WHERE expired_at IS NOT NULL"
46
+ assert q.params == []
47
+
48
+ def test_where_multiple_params(self):
49
+ q = Delete().From("t").Where("a = ? AND b = ?", 1, 2)
50
+ assert q.params == [1, 2]
51
+
52
+ def test_no_where_absent(self):
53
+ q = Delete().From("users")
54
+ assert "WHERE" not in q.sql
55
+
56
+
57
+ class TestDeleteLimit:
58
+ def test_limit(self):
59
+ q = Delete().From("logs").Limit(100)
60
+ assert "LIMIT 100" in q.sql
61
+
62
+ def test_no_order_by_absent(self):
63
+ q = Delete().From("users")
64
+ assert "ORDER BY" not in q.sql
65
+
66
+ def test_no_limit_absent(self):
67
+ q = Delete().From("users")
68
+ assert "LIMIT" not in q.sql
69
+
70
+
71
+ class TestDeleteChaining:
72
+ def test_method_chaining_returns_delete(self):
73
+ q = Delete()
74
+ assert q.From("t") is q
75
+ assert q.Where("1=1") is q
76
+ assert q.Limit(5) is q
77
+
78
+ def test_full_query(self):
79
+ q = Delete().From("logs").Where("level = ?", "debug").Limit(500)
80
+ assert q.sql == "DELETE FROM logs WHERE level = ? LIMIT 500"
81
+ assert q.params == ["debug"]
@@ -0,0 +1,60 @@
1
+ import pytest
2
+
3
+ from sqlqb import Insert
4
+
5
+
6
+ class TestInsertBasic:
7
+ def test_single_row(self):
8
+ q = Insert().Into("users").Values(name="Alice", email="alice@example.com")
9
+ assert q.sql == "INSERT INTO users (name, email) VALUES (?, ?)"
10
+
11
+ def test_str_equals_sql(self):
12
+ q = Insert().Into("users").Values(name="Alice")
13
+ assert str(q) == q.sql
14
+
15
+ def test_params_single_row(self):
16
+ q = Insert().Into("users").Values(name="Alice", email="alice@example.com")
17
+ assert q.params == ["Alice", "alice@example.com"]
18
+
19
+ def test_multiple_rows(self):
20
+ q = Insert().Into("users").Values(name="Alice").Values(name="Bob")
21
+ assert q.sql == "INSERT INTO users (name) VALUES (?), (?)"
22
+ assert q.params == ["Alice", "Bob"]
23
+
24
+ def test_multiple_columns_multiple_rows(self):
25
+ q = Insert().Into("orders").Values(user_id=1, total=99).Values(user_id=2, total=50)
26
+ assert q.sql == "INSERT INTO orders (user_id, total) VALUES (?, ?), (?, ?)"
27
+ assert q.params == [1, 99, 2, 50]
28
+
29
+ def test_method_chaining_returns_insert(self):
30
+ q = Insert()
31
+ assert q.Into("t") is q
32
+ assert q.Values(x=1) is q
33
+
34
+ def test_no_table_raises(self):
35
+ with pytest.raises(ValueError):
36
+ Insert().Values(name="Alice").sql
37
+
38
+ def test_no_values_raises(self):
39
+ with pytest.raises(ValueError):
40
+ Insert().Into("users").sql
41
+
42
+ def test_empty_table_raises(self):
43
+ with pytest.raises(ValueError):
44
+ Insert().Into("")
45
+
46
+ def test_blank_table_raises(self):
47
+ with pytest.raises(ValueError):
48
+ Insert().Into(" ")
49
+
50
+ def test_empty_values_raises(self):
51
+ with pytest.raises(ValueError):
52
+ Insert().Into("users").Values()
53
+
54
+ def test_params_empty_before_values(self):
55
+ q = Insert().Into("users")
56
+ assert q.params == []
57
+
58
+ def test_various_value_types(self):
59
+ q = Insert().Into("events").Values(user_id=42, active=True, score=3.14, label=None)
60
+ assert q.params == [42, True, 3.14, None]