sqlqb 0.1.3__tar.gz → 0.1.4__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.4/.envrc ADDED
@@ -0,0 +1,3 @@
1
+ layout_ide
2
+
3
+ export PYTHONSTARTUP=.pythonrc.py
@@ -0,0 +1 @@
1
+ from sqlqb import * # noqa: F403
@@ -1,5 +1,5 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlqb
3
- Version: 0.1.3
3
+ Version: 0.1.4
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.3"
3
+ version = "0.1.4"
4
4
  description = "A simple sql query builder"
5
5
  readme = "README.md"
6
6
  authors = []
@@ -6,6 +6,7 @@ class Delete:
6
6
  self.__table: str | None = None
7
7
  self.__wheres: list[str] = []
8
8
  self.__params: list = []
9
+ self.__named_params: dict = {}
9
10
  self.__limit: int | None = None
10
11
 
11
12
  def From(self, table: str) -> "Delete":
@@ -14,9 +15,18 @@ class Delete:
14
15
  self.__table = table
15
16
  return self
16
17
 
17
- def Where(self, condition: str, *args) -> "Delete":
18
+ def Where(self, condition: str, *args, **kwargs) -> "Delete":
19
+ if args and kwargs:
20
+ raise ValueError("error: cannot mix positional and named params")
21
+ if args and self.__named_params:
22
+ raise ValueError("error: cannot mix positional and named params")
23
+ if kwargs and self.__params:
24
+ raise ValueError("error: cannot mix positional and named params")
18
25
  self.__wheres.append(condition)
19
- self.__params += args
26
+ if kwargs:
27
+ self.__named_params.update(kwargs)
28
+ else:
29
+ self.__params += list(args)
20
30
  return self
21
31
 
22
32
  def Limit(self, limit: int) -> "Delete":
@@ -24,7 +34,9 @@ class Delete:
24
34
  return self
25
35
 
26
36
  @property
27
- def params(self) -> list:
37
+ def params(self) -> list | dict:
38
+ if self.__named_params:
39
+ return self.__named_params
28
40
  return self.__params
29
41
 
30
42
  @property
@@ -1,3 +1,5 @@
1
+ import re
2
+
1
3
  from pymysql.connections import Connection as _Connection
2
4
  from pymysql.cursors import DictCursor
3
5
 
@@ -7,25 +9,35 @@ from sqlqb import Update as _Update
7
9
  from sqlqb import Delete as _Delete
8
10
 
9
11
 
12
+ def _to_pymysql(sql: str, params: list | dict) -> tuple[str, list | dict]:
13
+ """Convert :name placeholders to %(name)s style for PyMySQL when params is a dict."""
14
+ if isinstance(params, dict):
15
+ sql = re.sub(r":([a-zA-Z_][a-zA-Z0-9_]*)", r"%(\1)s", sql)
16
+ return sql, params
17
+
18
+
10
19
  class Select(_Select):
11
20
  def __init__(self, connection: _Connection, *args):
12
21
  super().__init__(*args)
13
22
  self.__connection = connection
14
23
 
15
24
  def execute(self):
25
+ sql, params = _to_pymysql(self.sql, self.params)
16
26
  cursor = self.__connection.cursor()
17
- cursor.execute(self.sql, self.params)
27
+ cursor.execute(sql, params)
18
28
  return cursor
19
29
 
20
30
  def fetchone(self):
31
+ sql, params = _to_pymysql(self.sql, self.params)
21
32
  with self.__connection.cursor() as cursor:
22
- cursor.execute(self.sql, self.params)
33
+ cursor.execute(sql, params)
23
34
  result = cursor.fetchone()
24
35
  return result
25
36
 
26
37
  def fetchall(self):
38
+ sql, params = _to_pymysql(self.sql, self.params)
27
39
  with self.__connection.cursor() as cursor:
28
- cursor.execute(self.sql, self.params)
40
+ cursor.execute(sql, params)
29
41
  result = cursor.fetchall()
30
42
  return result
31
43
 
@@ -36,8 +48,9 @@ class Insert(_Insert):
36
48
  self.__connection = connection
37
49
 
38
50
  def execute(self) -> int:
51
+ sql, params = _to_pymysql(self.sql, self.params)
39
52
  with self.__connection.cursor() as cursor:
40
- cursor.execute(self.sql, self.params)
53
+ cursor.execute(sql, params)
41
54
  return cursor.rowcount
42
55
 
43
56
 
@@ -47,8 +60,9 @@ class Update(_Update):
47
60
  self.__connection = connection
48
61
 
49
62
  def execute(self) -> int:
63
+ sql, params = _to_pymysql(self.sql, self.params)
50
64
  with self.__connection.cursor() as cursor:
51
- cursor.execute(self.sql, self.params)
65
+ cursor.execute(sql, params)
52
66
  return cursor.rowcount
53
67
 
54
68
 
@@ -58,8 +72,9 @@ class Delete(_Delete):
58
72
  self.__connection = connection
59
73
 
60
74
  def execute(self) -> int:
75
+ sql, params = _to_pymysql(self.sql, self.params)
61
76
  with self.__connection.cursor() as cursor:
62
- cursor.execute(self.sql, self.params)
77
+ cursor.execute(sql, params)
63
78
  return cursor.rowcount
64
79
 
65
80
 
@@ -7,6 +7,7 @@ class Select:
7
7
  self.__table: str | None = None
8
8
  self.__wheres: list[str] = []
9
9
  self.__params: list = []
10
+ self.__named_params: dict = {}
10
11
  self.__joins: list[str] = []
11
12
  self.__orderby: list[tuple[str, str]] = []
12
13
  self.__groupby: list[str] = []
@@ -25,14 +26,32 @@ class Select:
25
26
  self.__table = table
26
27
  return self
27
28
 
28
- def Join(self, join: str, *args) -> "Select":
29
+ def Join(self, join: str, *args, **kwargs) -> "Select":
30
+ if args and kwargs:
31
+ raise ValueError("error: cannot mix positional and named params")
32
+ if args and self.__named_params:
33
+ raise ValueError("error: cannot mix positional and named params")
34
+ if kwargs and self.__params:
35
+ raise ValueError("error: cannot mix positional and named params")
29
36
  self.__joins.append(join)
30
- self.__params += args
37
+ if kwargs:
38
+ self.__named_params.update(kwargs)
39
+ else:
40
+ self.__params += list(args)
31
41
  return self
32
42
 
33
- def Where(self, condition: str, *args) -> "Select":
43
+ def Where(self, condition: str, *args, **kwargs) -> "Select":
44
+ if args and kwargs:
45
+ raise ValueError("error: cannot mix positional and named params")
46
+ if args and self.__named_params:
47
+ raise ValueError("error: cannot mix positional and named params")
48
+ if kwargs and self.__params:
49
+ raise ValueError("error: cannot mix positional and named params")
34
50
  self.__wheres.append(condition)
35
- self.__params += args
51
+ if kwargs:
52
+ self.__named_params.update(kwargs)
53
+ else:
54
+ self.__params += list(args)
36
55
  return self
37
56
 
38
57
  def OrderBy(self, column: str, direction: str = "ASC") -> "Select":
@@ -54,7 +73,9 @@ class Select:
54
73
  return self
55
74
 
56
75
  @property
57
- def params(self) -> list:
76
+ def params(self) -> list | dict:
77
+ if self.__named_params:
78
+ return self.__named_params
58
79
  return self.__params
59
80
 
60
81
  @property
@@ -62,8 +83,9 @@ class Select:
62
83
  sql = io.StringIO()
63
84
  sql.write("SELECT ")
64
85
  sql.write(", ".join(self.__columns or "*"))
65
- sql.write(" FROM ")
66
- sql.write(self.__table)
86
+ if self.__table:
87
+ sql.write(" FROM ")
88
+ sql.write(self.__table)
67
89
  if self.__joins:
68
90
  sql.write(" ")
69
91
  sql.write(" ".join(self.__joins))
@@ -9,8 +9,10 @@ from sqlqb import Update as _Update
9
9
  from sqlqb import Delete as _Delete
10
10
 
11
11
 
12
- def _bind(sql: str, params: list) -> tuple[str, dict]:
13
- """Replace ? placeholders with :p0, :p1, ... for SQLAlchemy text()."""
12
+ def _bind(sql: str, params: list | dict) -> tuple[str, dict]:
13
+ """Convert params for SQLAlchemy text(): pass dicts through, replace ? with :p0/:p1/... for lists."""
14
+ if isinstance(params, dict):
15
+ return sql, params
14
16
  named: dict = {}
15
17
  for i, val in enumerate(params):
16
18
  key = f"p{i}"
@@ -9,6 +9,7 @@ class Update:
9
9
  self.__sets: dict[str, object] = {}
10
10
  self.__wheres: list[str] = []
11
11
  self.__where_params: list = []
12
+ self.__where_named_params: dict = {}
12
13
  self.__limit: int | None = None
13
14
 
14
15
  def Set(self, **kwargs) -> "Update":
@@ -17,9 +18,18 @@ class Update:
17
18
  self.__sets.update(kwargs)
18
19
  return self
19
20
 
20
- def Where(self, condition: str, *args) -> "Update":
21
+ def Where(self, condition: str, *args, **kwargs) -> "Update":
22
+ if args and kwargs:
23
+ raise ValueError("error: cannot mix positional and named params")
24
+ if args and self.__where_named_params:
25
+ raise ValueError("error: cannot mix positional and named params")
26
+ if kwargs and self.__where_params:
27
+ raise ValueError("error: cannot mix positional and named params")
21
28
  self.__wheres.append(condition)
22
- self.__where_params += args
29
+ if kwargs:
30
+ self.__where_named_params.update(kwargs)
31
+ else:
32
+ self.__where_params += list(args)
23
33
  return self
24
34
 
25
35
  def Limit(self, limit: int) -> "Update":
@@ -27,9 +37,13 @@ class Update:
27
37
  return self
28
38
 
29
39
  @property
30
- def params(self) -> list:
40
+ def params(self) -> list | dict:
31
41
  if not self.__sets:
32
42
  return []
43
+ if self.__where_named_params:
44
+ named = dict(self.__sets)
45
+ named.update(self.__where_named_params)
46
+ return named
33
47
  return list(self.__sets.values()) + self.__where_params
34
48
 
35
49
  @property
@@ -40,7 +54,10 @@ class Update:
40
54
  sql.write("UPDATE ")
41
55
  sql.write(self.__table)
42
56
  sql.write(" SET ")
43
- sql.write(", ".join([f"{col} = ?" for col in self.__sets]))
57
+ if self.__where_named_params:
58
+ sql.write(", ".join([f"{col} = :{col}" for col in self.__sets]))
59
+ else:
60
+ sql.write(", ".join([f"{col} = ?" for col in self.__sets]))
44
61
  if self.__wheres:
45
62
  sql.write(" WHERE ")
46
63
  sql.write(" AND ".join(self.__wheres))
@@ -1,11 +1,11 @@
1
1
  import pytest
2
2
 
3
- from sqlqb import sqlite
3
+ from sqlqb import sqlite3
4
4
 
5
5
 
6
6
  @pytest.fixture
7
7
  def conn():
8
- c = sqlite.connect(":memory:")
8
+ c = sqlite3.connect(":memory:")
9
9
  c.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
10
10
  c.executemany("INSERT INTO users VALUES (?, ?, ?)", [(1, "Alice", 30), (2, "Bob", 17), (3, "Carol", 25)])
11
11
  c.commit()
@@ -101,7 +101,7 @@ class TestDelete:
101
101
 
102
102
  def test_delete_with_limit_not_supported(self, conn):
103
103
  # NOTE: sqlite library of python does not support LIMIT in delete queries
104
- import sqlite3
104
+ import sqlite3 as stdlib_sqlite3
105
105
 
106
- with pytest.raises(sqlite3.OperationalError):
106
+ with pytest.raises(stdlib_sqlite3.OperationalError):
107
107
  conn.Delete().From("users").Limit(2).execute()
@@ -293,7 +293,7 @@ wheels = [
293
293
 
294
294
  [[package]]
295
295
  name = "sqlqb"
296
- version = "0.1.3"
296
+ version = "0.1.4"
297
297
  source = { editable = "." }
298
298
 
299
299
  [package.dev-dependencies]
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