sqlite-persist 0.1.0__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.
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: sqlite-persist
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Classe de base ORM légère pour SQLite
|
|
5
|
+
Requires-Dist: pytest ; extra == 'dev'
|
|
6
|
+
Requires-Dist: pytest-cov ; extra == 'dev'
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# sqlite-persistence
|
|
12
|
+
|
|
13
|
+
Classe de base ORM légère pour SQLite, sans dépendances externes.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv add git+https://github.com/vous/sqlite-persist.git
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage minimal
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from sqlite_persist import Persistence
|
|
25
|
+
import sqlite3
|
|
26
|
+
|
|
27
|
+
con = sqlite3.connect("ma_base.db")
|
|
28
|
+
|
|
29
|
+
class User(Persistence):
|
|
30
|
+
def __init__(self, id, username, password):
|
|
31
|
+
self.id = id
|
|
32
|
+
self.username = username
|
|
33
|
+
self.password = password
|
|
34
|
+
|
|
35
|
+
# Lecture
|
|
36
|
+
user = User.get(1, con=con)
|
|
37
|
+
users = User.get_by({"username": "alice"}, con=con)
|
|
38
|
+
users = User.get_by({"username": "alice"}, order_by="username", con=con)
|
|
39
|
+
|
|
40
|
+
# Écriture
|
|
41
|
+
user.save(con=con) # insert ou replace selon présence de la PK
|
|
42
|
+
user.update(con=con) # update strict (la ligne doit exister)
|
|
43
|
+
user.insert(con=con) # insert, met à jour self.id après
|
|
44
|
+
user.delete(con=con)
|
|
45
|
+
|
|
46
|
+
# Batch
|
|
47
|
+
User.save_all([u1, u2, u3], con=con)
|
|
48
|
+
User.delete_all([u1, u2], con=con)
|
|
49
|
+
User.delete_where({"username": "bob"}, con=con)
|
|
50
|
+
User.update_where(values={"password": "..."}, where={"username": "alice"}, con=con)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Injection automatique de connexion (ex. Flask)
|
|
54
|
+
|
|
55
|
+
Créer une classe intermédiaire qui surcharge `_con()` :
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from sqlite_persistence import Persistence
|
|
59
|
+
from mon_app.db import get_db
|
|
60
|
+
|
|
61
|
+
class FlaskPersistence(Persistence):
|
|
62
|
+
@classmethod
|
|
63
|
+
def _con(cls):
|
|
64
|
+
return get_db()
|
|
65
|
+
|
|
66
|
+
class User(FlaskPersistence):
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
# con= n'est plus nécessaire
|
|
70
|
+
user = User.get(1)
|
|
71
|
+
user.save()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Clé primaire composite
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
class Day(FlaskPersistence):
|
|
78
|
+
_pk = ("month", "day")
|
|
79
|
+
|
|
80
|
+
day = Day.get("12", "01")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Attributs transitoires
|
|
84
|
+
|
|
85
|
+
Tout attribut commençant par `_` est ignoré lors des opérations SQL :
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
class User(FlaskPersistence):
|
|
89
|
+
_table = "user" # si le nom de table diffère de la classe, par défaut le nom de la classe
|
|
90
|
+
|
|
91
|
+
def __init__(self, id, name):
|
|
92
|
+
self.id = id
|
|
93
|
+
self.name = name
|
|
94
|
+
self._transient = None # jamais envoyé en base
|
|
95
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# sqlite-persistence
|
|
2
|
+
|
|
3
|
+
Classe de base ORM légère pour SQLite, sans dépendances externes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv add git+https://github.com/vous/sqlite-persist.git
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage minimal
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from sqlite_persist import Persistence
|
|
15
|
+
import sqlite3
|
|
16
|
+
|
|
17
|
+
con = sqlite3.connect("ma_base.db")
|
|
18
|
+
|
|
19
|
+
class User(Persistence):
|
|
20
|
+
def __init__(self, id, username, password):
|
|
21
|
+
self.id = id
|
|
22
|
+
self.username = username
|
|
23
|
+
self.password = password
|
|
24
|
+
|
|
25
|
+
# Lecture
|
|
26
|
+
user = User.get(1, con=con)
|
|
27
|
+
users = User.get_by({"username": "alice"}, con=con)
|
|
28
|
+
users = User.get_by({"username": "alice"}, order_by="username", con=con)
|
|
29
|
+
|
|
30
|
+
# Écriture
|
|
31
|
+
user.save(con=con) # insert ou replace selon présence de la PK
|
|
32
|
+
user.update(con=con) # update strict (la ligne doit exister)
|
|
33
|
+
user.insert(con=con) # insert, met à jour self.id après
|
|
34
|
+
user.delete(con=con)
|
|
35
|
+
|
|
36
|
+
# Batch
|
|
37
|
+
User.save_all([u1, u2, u3], con=con)
|
|
38
|
+
User.delete_all([u1, u2], con=con)
|
|
39
|
+
User.delete_where({"username": "bob"}, con=con)
|
|
40
|
+
User.update_where(values={"password": "..."}, where={"username": "alice"}, con=con)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Injection automatique de connexion (ex. Flask)
|
|
44
|
+
|
|
45
|
+
Créer une classe intermédiaire qui surcharge `_con()` :
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from sqlite_persistence import Persistence
|
|
49
|
+
from mon_app.db import get_db
|
|
50
|
+
|
|
51
|
+
class FlaskPersistence(Persistence):
|
|
52
|
+
@classmethod
|
|
53
|
+
def _con(cls):
|
|
54
|
+
return get_db()
|
|
55
|
+
|
|
56
|
+
class User(FlaskPersistence):
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
# con= n'est plus nécessaire
|
|
60
|
+
user = User.get(1)
|
|
61
|
+
user.save()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Clé primaire composite
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
class Day(FlaskPersistence):
|
|
68
|
+
_pk = ("month", "day")
|
|
69
|
+
|
|
70
|
+
day = Day.get("12", "01")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Attributs transitoires
|
|
74
|
+
|
|
75
|
+
Tout attribut commençant par `_` est ignoré lors des opérations SQL :
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
class User(FlaskPersistence):
|
|
79
|
+
_table = "user" # si le nom de table diffère de la classe, par défaut le nom de la classe
|
|
80
|
+
|
|
81
|
+
def __init__(self, id, name):
|
|
82
|
+
self.id = id
|
|
83
|
+
self.name = name
|
|
84
|
+
self._transient = None # jamais envoyé en base
|
|
85
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sqlite-persist"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Classe de base ORM légère pour SQLite"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[project.optional-dependencies]
|
|
10
|
+
dev = ["pytest", "pytest-cov"]
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build>=0.10.4,<0.11.0"]
|
|
14
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from collections.abc import Collection
|
|
5
|
+
from typing import Any, ClassVar, Self
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Persistence:
|
|
9
|
+
_columns_cache: ClassVar[dict[str, set[str]]] = {}
|
|
10
|
+
_pk: str | tuple[str, ...] = "id"
|
|
11
|
+
_table: ClassVar[str | None] = None
|
|
12
|
+
|
|
13
|
+
# ── Utilitaires internes ───────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def _pk_columns(cls) -> tuple[str, ...]:
|
|
17
|
+
return (cls._pk,) if isinstance(cls._pk, str) else tuple(cls._pk)
|
|
18
|
+
|
|
19
|
+
def _pk_dict(self) -> dict:
|
|
20
|
+
return {col: self.__dict__[col] for col in self._pk_columns()}
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def _pk_where_clause(cls) -> str:
|
|
24
|
+
return " AND ".join(f"{col}=:{col}" for col in cls._pk_columns())
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def _table_name(cls) -> str:
|
|
28
|
+
return cls._table or cls.__name__
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def _con(cls) -> sqlite3.Connection:
|
|
32
|
+
raise NotImplementedError("Passez con= explicitement ou surchargez _con().")
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def _check_columns(cls, con: sqlite3.Connection, keys: Collection) -> None:
|
|
36
|
+
if cls._table_name() not in cls._columns_cache:
|
|
37
|
+
cur = con.execute(f"SELECT name FROM pragma_table_info('{cls._table_name()}')") # noqa: S608
|
|
38
|
+
cls._columns_cache[cls._table_name()] = {row[0] for row in cur.fetchall()}
|
|
39
|
+
|
|
40
|
+
valid_columns = cls._columns_cache[cls._table_name()]
|
|
41
|
+
invalid = set(keys) - valid_columns
|
|
42
|
+
if invalid:
|
|
43
|
+
raise ValueError(f"Colonnes invalides : {invalid}")
|
|
44
|
+
|
|
45
|
+
def _row_data(self) -> dict:
|
|
46
|
+
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
|
47
|
+
|
|
48
|
+
def __repr__(self) -> str:
|
|
49
|
+
pk_cols = self._pk_columns()
|
|
50
|
+
pk_str = ", ".join(f"{col}={self.__dict__.get(col)!r}" for col in pk_cols)
|
|
51
|
+
rest_str = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items() if k not in pk_cols and not k.startswith("_"))
|
|
52
|
+
return f"{self._table_name()}[{pk_str} | {rest_str}]"
|
|
53
|
+
|
|
54
|
+
# ── Lecture ────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def get(cls, *pk_values, con: sqlite3.Connection | None = None) -> Self | None:
|
|
58
|
+
con: sqlite3.Connection = con or cls._con()
|
|
59
|
+
pk_cols = cls._pk_columns()
|
|
60
|
+
if len(pk_values) != len(pk_cols):
|
|
61
|
+
raise ValueError(f"{cls._table_name()} attend {len(pk_cols)} valeur(s) de PK : {pk_cols}")
|
|
62
|
+
params = dict(zip(pk_cols, pk_values, strict=False))
|
|
63
|
+
|
|
64
|
+
cursor = con.cursor()
|
|
65
|
+
cursor.row_factory = sqlite3.Row
|
|
66
|
+
row = cursor.execute(
|
|
67
|
+
f"SELECT * FROM {cls._table_name()} WHERE {cls._pk_where_clause()}", # noqa: S608
|
|
68
|
+
params,
|
|
69
|
+
).fetchone()
|
|
70
|
+
cursor.close()
|
|
71
|
+
|
|
72
|
+
if row is not None:
|
|
73
|
+
o: Self = object.__new__(cls)
|
|
74
|
+
o.__dict__.update(dict(row))
|
|
75
|
+
return o
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def get_by(
|
|
80
|
+
cls,
|
|
81
|
+
parameters: dict[str, Any],
|
|
82
|
+
order_by: str | list[str] | None = None,
|
|
83
|
+
con: sqlite3.Connection | None = None,
|
|
84
|
+
) -> list[Self]:
|
|
85
|
+
con: sqlite3.Connection = con or cls._con()
|
|
86
|
+
cls._check_columns(con, parameters.keys())
|
|
87
|
+
where = " AND ".join(f"{k}=:{k}" for k in parameters)
|
|
88
|
+
query = f"SELECT * FROM {cls._table_name()} WHERE {where}" # noqa: S608
|
|
89
|
+
|
|
90
|
+
if order_by is not None:
|
|
91
|
+
cols = [order_by] if isinstance(order_by, str) else order_by
|
|
92
|
+
query += f" ORDER BY {', '.join(cols)}"
|
|
93
|
+
|
|
94
|
+
cursor = con.cursor()
|
|
95
|
+
cursor.row_factory = sqlite3.Row
|
|
96
|
+
rows = cursor.execute(query, parameters).fetchall()
|
|
97
|
+
cursor.close()
|
|
98
|
+
|
|
99
|
+
objects: list[Self] = []
|
|
100
|
+
for row in rows:
|
|
101
|
+
o: Self = object.__new__(cls)
|
|
102
|
+
o.__dict__.update(dict(row))
|
|
103
|
+
objects.append(o)
|
|
104
|
+
return objects
|
|
105
|
+
|
|
106
|
+
# ── Insertion ──────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
def insert(self, con: sqlite3.Connection | None = None) -> None:
|
|
109
|
+
con: sqlite3.Connection = con or self._con()
|
|
110
|
+
data: dict = self._row_data()
|
|
111
|
+
self._check_columns(con, data.keys())
|
|
112
|
+
cols = ", ".join(data.keys())
|
|
113
|
+
placeholders = ", ".join(f":{k}" for k in data)
|
|
114
|
+
cursor = con.execute(
|
|
115
|
+
f"INSERT INTO {self._table_name()} ({cols}) VALUES ({placeholders})", # noqa: S608
|
|
116
|
+
data,
|
|
117
|
+
)
|
|
118
|
+
con.commit()
|
|
119
|
+
if cursor.lastrowid is not None:
|
|
120
|
+
pk_cols = self._pk_columns()
|
|
121
|
+
self.__dict__[pk_cols[0]] = cursor.lastrowid
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def insert_all(cls, items: list[Self], con: sqlite3.Connection | None = None) -> None:
|
|
125
|
+
con: sqlite3.Connection = con or cls._con()
|
|
126
|
+
if not items:
|
|
127
|
+
return
|
|
128
|
+
sample = items[0]._row_data()
|
|
129
|
+
cls._check_columns(con, sample.keys())
|
|
130
|
+
cols = ", ".join(sample.keys())
|
|
131
|
+
placeholders = ", ".join(f":{k}" for k in sample)
|
|
132
|
+
cursor = con.executemany(
|
|
133
|
+
f"INSERT INTO {cls._table_name()} ({cols}) VALUES ({placeholders})", # noqa: S608
|
|
134
|
+
[item._row_data() for item in items],
|
|
135
|
+
)
|
|
136
|
+
con.commit()
|
|
137
|
+
if cursor.lastrowid is not None:
|
|
138
|
+
first_id = cursor.lastrowid - len(items) + 1
|
|
139
|
+
pk_col = cls._pk_columns()[0]
|
|
140
|
+
for item, generated_id in zip(items, range(first_id, cursor.lastrowid + 1), strict=False):
|
|
141
|
+
item.__dict__[pk_col] = generated_id
|
|
142
|
+
|
|
143
|
+
# ── Mise à jour ────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
def update(self, con: sqlite3.Connection | None = None) -> None:
|
|
146
|
+
con: sqlite3.Connection = con or self._con()
|
|
147
|
+
data: dict = self._row_data()
|
|
148
|
+
self._check_columns(con, data.keys())
|
|
149
|
+
pk_cols = self._pk_columns()
|
|
150
|
+
missing = [col for col in pk_cols if col not in data]
|
|
151
|
+
if missing:
|
|
152
|
+
raise ValueError(f"Colonnes PK manquantes dans l'instance : {missing}")
|
|
153
|
+
set_clause = ", ".join(f"{k}=:{k}" for k in data if k not in pk_cols)
|
|
154
|
+
con.execute(
|
|
155
|
+
f"UPDATE {self._table_name()} SET {set_clause} WHERE {self._pk_where_clause()}", # noqa: S608
|
|
156
|
+
data,
|
|
157
|
+
)
|
|
158
|
+
con.commit()
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def update_where(cls, values: dict[str, Any], where: dict[str, Any], con: sqlite3.Connection | None = None) -> None:
|
|
162
|
+
con: sqlite3.Connection = con or cls._con()
|
|
163
|
+
cls._check_columns(con, list(values.keys()) + list(where.keys()))
|
|
164
|
+
set_clause = ", ".join(f"{k}=:set_{k}" for k in values)
|
|
165
|
+
where_clause = " AND ".join(f"{k}=:where_{k}" for k in where)
|
|
166
|
+
params = {f"set_{k}": v for k, v in values.items()} | {f"where_{k}": v for k, v in where.items()}
|
|
167
|
+
con.execute(
|
|
168
|
+
f"UPDATE {cls._table_name()} SET {set_clause} WHERE {where_clause}", # noqa: S608
|
|
169
|
+
params,
|
|
170
|
+
)
|
|
171
|
+
con.commit()
|
|
172
|
+
|
|
173
|
+
@classmethod
|
|
174
|
+
def update_all(cls, items: list[Self], con: sqlite3.Connection | None = None) -> None:
|
|
175
|
+
con: sqlite3.Connection = con or cls._con()
|
|
176
|
+
if not items:
|
|
177
|
+
return
|
|
178
|
+
pk_cols = cls._pk_columns()
|
|
179
|
+
sample = items[0]._row_data()
|
|
180
|
+
cls._check_columns(con, sample.keys())
|
|
181
|
+
missing = [col for col in pk_cols if col not in sample]
|
|
182
|
+
if missing:
|
|
183
|
+
raise ValueError(f"Colonnes PK manquantes : {missing}")
|
|
184
|
+
set_clause = ", ".join(f"{k}=:{k}" for k in sample if k not in pk_cols)
|
|
185
|
+
con.executemany(
|
|
186
|
+
f"UPDATE {cls._table_name()} SET {set_clause} WHERE {cls._pk_where_clause()}", # noqa: S608
|
|
187
|
+
[item._row_data() for item in items],
|
|
188
|
+
)
|
|
189
|
+
con.commit()
|
|
190
|
+
|
|
191
|
+
# ── Suppression ────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
def delete(self, con: sqlite3.Connection | None = None) -> None:
|
|
194
|
+
con: sqlite3.Connection = con or self._con()
|
|
195
|
+
con.execute(
|
|
196
|
+
f"DELETE FROM {self._table_name()} WHERE {self._pk_where_clause()}", # noqa: S608
|
|
197
|
+
self._pk_dict(),
|
|
198
|
+
)
|
|
199
|
+
con.commit()
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def delete_where(cls, parameters: dict[str, Any], con: sqlite3.Connection | None = None) -> None:
|
|
203
|
+
con: sqlite3.Connection = con or cls._con()
|
|
204
|
+
cls._check_columns(con, parameters.keys())
|
|
205
|
+
where_clause = " AND ".join(f"{k}=:{k}" for k in parameters)
|
|
206
|
+
con.execute(
|
|
207
|
+
f"DELETE FROM {cls._table_name()} WHERE {where_clause}", # noqa: S608
|
|
208
|
+
parameters,
|
|
209
|
+
)
|
|
210
|
+
con.commit()
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def delete_all(cls, items: list[Self], con: sqlite3.Connection | None = None) -> None:
|
|
214
|
+
con: sqlite3.Connection = con or cls._con()
|
|
215
|
+
if not items:
|
|
216
|
+
return
|
|
217
|
+
con.executemany(
|
|
218
|
+
f"DELETE FROM {cls._table_name()} WHERE {cls._pk_where_clause()}", # noqa: S608
|
|
219
|
+
[item._pk_dict() for item in items],
|
|
220
|
+
)
|
|
221
|
+
con.commit()
|
|
222
|
+
|
|
223
|
+
# ── Upsert ────────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
def upsert(self, con: sqlite3.Connection | None = None) -> None:
|
|
226
|
+
con: sqlite3.Connection = con or self._con()
|
|
227
|
+
data: dict = self._row_data()
|
|
228
|
+
self._check_columns(con, data.keys())
|
|
229
|
+
cols = ", ".join(data.keys())
|
|
230
|
+
placeholders = ", ".join(f":{k}" for k in data)
|
|
231
|
+
cursor = con.execute(
|
|
232
|
+
f"INSERT OR REPLACE INTO {self._table_name()} ({cols}) VALUES ({placeholders})", # noqa: S608
|
|
233
|
+
data,
|
|
234
|
+
)
|
|
235
|
+
con.commit()
|
|
236
|
+
# Récupère l'id généré uniquement si la PK était absente
|
|
237
|
+
pk_cols = self._pk_columns()
|
|
238
|
+
if len(pk_cols) == 1 and self.__dict__.get(pk_cols[0]) is None:
|
|
239
|
+
self.__dict__[pk_cols[0]] = cursor.lastrowid
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def upsert_all(cls, items: list[Self], con: sqlite3.Connection | None = None) -> None:
|
|
243
|
+
if not items:
|
|
244
|
+
return
|
|
245
|
+
con: sqlite3.Connection = con or cls._con()
|
|
246
|
+
sample = items[0]._row_data()
|
|
247
|
+
cls._check_columns(con, sample.keys())
|
|
248
|
+
cols = ", ".join(sample.keys())
|
|
249
|
+
placeholders = ", ".join(f":{k}" for k in sample)
|
|
250
|
+
con.executemany(
|
|
251
|
+
f"INSERT OR REPLACE INTO {cls._table_name()} ({cols}) VALUES ({placeholders})", # noqa: S608
|
|
252
|
+
[item._row_data() for item in items],
|
|
253
|
+
)
|
|
254
|
+
con.commit()
|
|
File without changes
|