sqlite-sh 0.1.0__py3-none-any.whl

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.
sqlite_sh/storage.py ADDED
@@ -0,0 +1,269 @@
1
+ import sqlite3
2
+ from pathlib import Path
3
+
4
+ from .config import DB_FILE
5
+
6
+
7
+ def connect():
8
+ conn = sqlite3.connect(DB_FILE)
9
+ conn.row_factory = sqlite3.Row
10
+ conn.execute("PRAGMA foreign_keys = ON")
11
+ return conn
12
+
13
+
14
+ def require_database():
15
+ if not Path(DB_FILE).exists():
16
+ raise FileNotFoundError("Database not found. Run: python bootstrap_db.py")
17
+
18
+
19
+ def authenticate(login, password):
20
+ with connect() as conn:
21
+ return conn.execute(
22
+ """
23
+ SELECT users.id, users.full_name, users.login, roles.name AS role
24
+ FROM users
25
+ JOIN roles ON roles.id = users.role_id
26
+ WHERE users.login = ? AND users.password = ?
27
+ """,
28
+ (login, password),
29
+ ).fetchone()
30
+
31
+
32
+ def list_products(search="", supplier="", sort_stock=""):
33
+ clauses = []
34
+ params = []
35
+ if supplier and supplier != "Все поставщики":
36
+ clauses.append("suppliers.name = ?")
37
+ params.append(supplier)
38
+ if search:
39
+ like = f"%{search.lower()}%"
40
+ text_fields = [
41
+ "products.article",
42
+ "products.name",
43
+ "units.name",
44
+ "suppliers.name",
45
+ "manufacturers.name",
46
+ "categories.name",
47
+ "products.description",
48
+ ]
49
+ clauses.append("(" + " OR ".join(f"LOWER({field}) LIKE ?" for field in text_fields) + ")")
50
+ params.extend([like] * len(text_fields))
51
+ where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
52
+ order = ""
53
+ if sort_stock == "Остаток по возрастанию":
54
+ order = "ORDER BY products.stock ASC"
55
+ elif sort_stock == "Остаток по убыванию":
56
+ order = "ORDER BY products.stock DESC"
57
+ else:
58
+ order = "ORDER BY products.id ASC"
59
+ with connect() as conn:
60
+ return conn.execute(
61
+ f"""
62
+ SELECT
63
+ products.id, products.article, products.name, products.price,
64
+ products.discount, products.stock, products.description, products.image_path,
65
+ units.name AS unit, suppliers.name AS supplier,
66
+ manufacturers.name AS manufacturer, categories.name AS category
67
+ FROM products
68
+ JOIN units ON units.id = products.unit_id
69
+ JOIN suppliers ON suppliers.id = products.supplier_id
70
+ JOIN manufacturers ON manufacturers.id = products.manufacturer_id
71
+ JOIN categories ON categories.id = products.category_id
72
+ {where}
73
+ {order}
74
+ """,
75
+ params,
76
+ ).fetchall()
77
+
78
+
79
+ def get_product(product_id):
80
+ with connect() as conn:
81
+ return conn.execute(
82
+ """
83
+ SELECT
84
+ products.*, units.name AS unit, suppliers.name AS supplier,
85
+ manufacturers.name AS manufacturer, categories.name AS category
86
+ FROM products
87
+ JOIN units ON units.id = products.unit_id
88
+ JOIN suppliers ON suppliers.id = products.supplier_id
89
+ JOIN manufacturers ON manufacturers.id = products.manufacturer_id
90
+ JOIN categories ON categories.id = products.category_id
91
+ WHERE products.id = ?
92
+ """,
93
+ (product_id,),
94
+ ).fetchone()
95
+
96
+
97
+ def options(table):
98
+ with connect() as conn:
99
+ if table == "pickup_points":
100
+ return [row["address"] for row in conn.execute("SELECT address FROM pickup_points ORDER BY address")]
101
+ return [row["name"] for row in conn.execute(f"SELECT name FROM {table} ORDER BY name")]
102
+
103
+
104
+ def get_or_create(conn, table, name):
105
+ conn.execute(f"INSERT OR IGNORE INTO {table}(name) VALUES (?)", (name.strip(),))
106
+ return conn.execute(f"SELECT id FROM {table} WHERE name = ?", (name.strip(),)).fetchone()[0]
107
+
108
+
109
+ def save_product(data, product_id=None):
110
+ with connect() as conn:
111
+ unit_id = get_or_create(conn, "units", data["unit"])
112
+ supplier_id = get_or_create(conn, "suppliers", data["supplier"])
113
+ manufacturer_id = get_or_create(conn, "manufacturers", data["manufacturer"])
114
+ category_id = get_or_create(conn, "categories", data["category"])
115
+ values = (
116
+ data["article"],
117
+ data["name"],
118
+ unit_id,
119
+ data["price"],
120
+ supplier_id,
121
+ manufacturer_id,
122
+ category_id,
123
+ data["discount"],
124
+ data["stock"],
125
+ data["description"],
126
+ data["image_path"],
127
+ )
128
+ if product_id:
129
+ conn.execute(
130
+ """
131
+ UPDATE products SET article=?, name=?, unit_id=?, price=?, supplier_id=?,
132
+ manufacturer_id=?, category_id=?, discount=?, stock=?, description=?, image_path=?
133
+ WHERE id=?
134
+ """,
135
+ values + (product_id,),
136
+ )
137
+ else:
138
+ product_id = conn.execute("SELECT COALESCE(MAX(id), 0) + 1 FROM products").fetchone()[0]
139
+ conn.execute(
140
+ """
141
+ INSERT INTO products(
142
+ id, article, name, unit_id, price, supplier_id, manufacturer_id,
143
+ category_id, discount, stock, description, image_path
144
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
145
+ """,
146
+ (product_id,) + values,
147
+ )
148
+ conn.commit()
149
+
150
+
151
+ def product_in_orders(product_id):
152
+ with connect() as conn:
153
+ count = conn.execute(
154
+ "SELECT COUNT(*) FROM order_items WHERE product_id = ?", (product_id,)
155
+ ).fetchone()[0]
156
+ return count > 0
157
+
158
+
159
+ def delete_product(product_id):
160
+ with connect() as conn:
161
+ conn.execute("DELETE FROM products WHERE id = ?", (product_id,))
162
+ conn.commit()
163
+
164
+
165
+ def suppliers_for_filter():
166
+ return ["Все поставщики"] + options("suppliers")
167
+
168
+
169
+ def list_orders():
170
+ with connect() as conn:
171
+ return conn.execute(
172
+ """
173
+ SELECT
174
+ orders.id, orders.order_date, orders.delivery_date, orders.client_name,
175
+ orders.receive_code, orders.status, pickup_points.address AS pickup_point,
176
+ GROUP_CONCAT(products.article || ' x' || order_items.quantity, ', ') AS items
177
+ FROM orders
178
+ LEFT JOIN pickup_points ON pickup_points.id = orders.pickup_point_id
179
+ LEFT JOIN order_items ON order_items.order_id = orders.id
180
+ LEFT JOIN products ON products.id = order_items.product_id
181
+ GROUP BY orders.id
182
+ ORDER BY orders.id
183
+ """
184
+ ).fetchall()
185
+
186
+
187
+ def get_order(order_id):
188
+ with connect() as conn:
189
+ order = conn.execute(
190
+ """
191
+ SELECT orders.*, pickup_points.address AS pickup_point
192
+ FROM orders
193
+ LEFT JOIN pickup_points ON pickup_points.id = orders.pickup_point_id
194
+ WHERE orders.id = ?
195
+ """,
196
+ (order_id,),
197
+ ).fetchone()
198
+ items = conn.execute(
199
+ """
200
+ SELECT products.article, order_items.quantity
201
+ FROM order_items
202
+ JOIN products ON products.id = order_items.product_id
203
+ WHERE order_items.order_id = ?
204
+ ORDER BY order_items.id
205
+ """,
206
+ (order_id,),
207
+ ).fetchall()
208
+ return order, items
209
+
210
+
211
+ def save_order(data, items, order_id=None):
212
+ with connect() as conn:
213
+ pickup_id = conn.execute(
214
+ "SELECT id FROM pickup_points WHERE address = ?", (data["pickup_point"],)
215
+ ).fetchone()
216
+ if pickup_id is None:
217
+ conn.execute("INSERT INTO pickup_points(address) VALUES (?)", (data["pickup_point"],))
218
+ pickup_id = conn.execute("SELECT last_insert_rowid()").fetchone()
219
+ if order_id:
220
+ final_id = order_id
221
+ conn.execute(
222
+ """
223
+ UPDATE orders SET order_date=?, delivery_date=?, pickup_point_id=?,
224
+ client_name=?, receive_code=?, status=?
225
+ WHERE id=?
226
+ """,
227
+ (
228
+ data["order_date"],
229
+ data["delivery_date"],
230
+ pickup_id[0],
231
+ data["client_name"],
232
+ data["receive_code"],
233
+ data["status"],
234
+ final_id,
235
+ ),
236
+ )
237
+ conn.execute("DELETE FROM order_items WHERE order_id = ?", (final_id,))
238
+ else:
239
+ final_id = conn.execute("SELECT COALESCE(MAX(id), 0) + 1 FROM orders").fetchone()[0]
240
+ conn.execute(
241
+ """
242
+ INSERT INTO orders(id, order_date, delivery_date, pickup_point_id, client_name, receive_code, status)
243
+ VALUES (?, ?, ?, ?, ?, ?, ?)
244
+ """,
245
+ (
246
+ final_id,
247
+ data["order_date"],
248
+ data["delivery_date"],
249
+ pickup_id[0],
250
+ data["client_name"],
251
+ data["receive_code"],
252
+ data["status"],
253
+ ),
254
+ )
255
+ for article, quantity in items:
256
+ product = conn.execute("SELECT id FROM products WHERE article = ?", (article,)).fetchone()
257
+ if product is None:
258
+ raise ValueError(f"Товар с артикулом {article} не найден")
259
+ conn.execute(
260
+ "INSERT INTO order_items(order_id, product_id, quantity) VALUES (?, ?, ?)",
261
+ (final_id, product[0], quantity),
262
+ )
263
+ conn.commit()
264
+
265
+
266
+ def delete_order(order_id):
267
+ with connect() as conn:
268
+ conn.execute("DELETE FROM orders WHERE id = ?", (order_id,))
269
+ conn.commit()
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlite-sh
3
+ Version: 0.1.0
4
+ Summary: SQLite-backed Tkinter shoe shop demo application
5
+ Author-email: adwedelf <adwedelf@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/tgoaty/demo-ex
8
+ Project-URL: Repository, https://github.com/tgoaty/demo-ex
9
+ Keywords: sqlite,tkinter,demo,shop
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Education
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Operating System :: OS Independent
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ # sqlite-sh
24
+
25
+ `sqlite-sh` is a small educational Tkinter desktop application backed by SQLite.
26
+ It includes a shoe shop catalog, role-based login, products, orders, database schema, and exam diagrams.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install sqlite-sh
32
+ ```
33
+
34
+ ## Run
35
+
36
+ ```bash
37
+ shop
38
+ ```
39
+
40
+ On first import/run, the packaged seed database is copied to:
41
+
42
+ ```text
43
+ ~/.sqlite_sh/shop.db
44
+ ```
45
+
46
+ You can override the writable database folder:
47
+
48
+ ```bash
49
+ SQLITE_SH_HOME=/path/to/folder shop
50
+ ```
51
+
52
+ ## Admin credentials
53
+
54
+ ```text
55
+ Login: 94d5ous@gmail.com
56
+ Password: uzWC67
57
+ ```
58
+
59
+ ## Included files
60
+
61
+ The installed package contains:
62
+
63
+ - `assets/` with icons and images;
64
+ - `data/shop.db` seed SQLite database;
65
+ - `database.sql` schema;
66
+ - `algorithm_flowchart_gost.pdf`;
67
+ - `db_uml_diagram.pdf`.
68
+
69
+ To find the installed package folder:
70
+
71
+ ```bash
72
+ python -c "import sqlite_sh, pathlib; print(pathlib.Path(sqlite_sh.__file__).parent)"
73
+ ```
74
+
75
+ ## Build locally
76
+
77
+ ```bash
78
+ python -m pip install --upgrade build twine
79
+ python -m build
80
+ python -m twine check dist/*
81
+ ```
82
+
83
+ ## Upload flow
84
+
85
+ TestPyPI first:
86
+
87
+ ```bash
88
+ python -m twine upload --repository testpypi dist/*
89
+ ```
90
+
91
+ PyPI:
92
+
93
+ ```bash
94
+ python -m twine upload dist/*
95
+ ```
@@ -0,0 +1,33 @@
1
+ sqlite_sh/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ sqlite_sh/__main__.py,sha256=U9tbyxnsu_jni-FKO7fDYgD5-PFIE9j0r1ToYuvkw6Q,79
3
+ sqlite_sh/algorithm_flowchart_gost.pdf,sha256=QgjFwdYBiC0PELJS-AwEalCAzCxNOh0Raj2QNG95g74,151433
4
+ sqlite_sh/bootstrap.py,sha256=XG5ns_fxXmy9-N22v6e-WQhPpZE-3PSiUVVcAFp1sP0,7892
5
+ sqlite_sh/config.py,sha256=FUiVPQXtpbVMhIAl1ijWQWFyby3xKCtakwjJS9ToaPo,575
6
+ sqlite_sh/database.sql,sha256=9KjyfwmwahtmU-Dn453XtKbwy9aeHyvX8oREhrNjpS8,2445
7
+ sqlite_sh/db_uml_diagram.pdf,sha256=jLVKE6dwNnDiTS6eov8ghxG6jSnmORemKYybBd58pf0,174891
8
+ sqlite_sh/diagram.py,sha256=DuGf83Ktstd1Frp6kcPst8wcp4loO017Ac9Mm12SRTg,1179
9
+ sqlite_sh/gui.py,sha256=yh93nAswsvWYA8JlT-D2NHORvtUH6ax3ZXUHgauLQxo,28704
10
+ sqlite_sh/storage.py,sha256=4VzPpggqmGi4UFRSh1yczf3OCaY0fNwVsHK0OQ1nrj4,9702
11
+ sqlite_sh/assets/1.jpg,sha256=6bLXV68p30nv6dqUr1e34YYPl1-OAmqZX6nJKyCqiYA,84670
12
+ sqlite_sh/assets/10.jpg,sha256=eKNjTHhaI89VfQp4whXKbbDbyd9BKcG5UQ-9wEKDhnI,84570
13
+ sqlite_sh/assets/2.jpg,sha256=tFChy8bxQrGV_kSKLXy54mp2Yi-wP2fp8dvOPe-hqs4,79395
14
+ sqlite_sh/assets/3.jpg,sha256=RCMu8-jiMTjj58eplzjs0RvuplInSDTpT-qyy4thago,93102
15
+ sqlite_sh/assets/4.jpg,sha256=7T4kpqSkSjS3QWFSGqmjgWKyQ6ofHcW-m-ddaKZxOX0,29364
16
+ sqlite_sh/assets/5.jpg,sha256=gbfJPqbz-61KUcsjMMTWHcahMd4Urtfb9_ERAp6a9hc,48093
17
+ sqlite_sh/assets/6.jpg,sha256=1_FIER3_syQvBqVpPi2X4ysbCA1wSeHH31aadTmUAaM,59703
18
+ sqlite_sh/assets/7.jpg,sha256=cqi9X1GcMNZAogXgGNsWLivCuPUe8jBYGBUaJ2nQ0do,74174
19
+ sqlite_sh/assets/8.jpg,sha256=K8H6svovh2pVFw5AenPI8QXyys02g94CuKERY4LI6Kw,57007
20
+ sqlite_sh/assets/9.jpg,sha256=ZRrOTkMtQxxyo3xSiqOiF9C70drDVfvKlLnQAIPc6No,70100
21
+ sqlite_sh/assets/Icon.JPG,sha256=nd1PWOFWjTqmFVnAW4LVx-xnB-nJM-6C7Mn00B8b1ZY,28604
22
+ sqlite_sh/assets/Icon.ico,sha256=WTA4NlVIqihxxQWcllbCOkqrYM7-cwoP0hCHj2a9Lnw,147115
23
+ sqlite_sh/assets/Icon.png,sha256=WTA4NlVIqihxxQWcllbCOkqrYM7-cwoP0hCHj2a9Lnw,147115
24
+ sqlite_sh/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ sqlite_sh/assets/picture.png,sha256=KQAL9ak0PfiOEbEM9WBw0UwuBmtKgtp8WUdL_LpqekQ,13344
26
+ sqlite_sh/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ sqlite_sh/data/shop.db,sha256=P0xmkXk1B1jNYah5NZZOpK1Md8qDC4By8GXpp0wKN-Q,98304
28
+ sqlite_sh-0.1.0.dist-info/licenses/LICENSE,sha256=IYEQZgC5OsWP5NwcQZtA4NTsnZxF8fomIawTypF1pyM,1065
29
+ sqlite_sh-0.1.0.dist-info/METADATA,sha256=cCykaK9UHTBa02n3v9I9QAWTTgYCHHabsyqAfnyeQ2o,2004
30
+ sqlite_sh-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
31
+ sqlite_sh-0.1.0.dist-info/entry_points.txt,sha256=qDEPgj1rmladj-v4R2rNEsN5BroheRrh5wlFc5ZVwsU,134
32
+ sqlite_sh-0.1.0.dist-info/top_level.txt,sha256=NYTwravGeQ6GJc86cODIeCrdt3FF0-emzNZ3qdRL5kI,10
33
+ sqlite_sh-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ shop = sqlite_sh.gui:main
3
+ sqlite-sh-bootstrap = sqlite_sh.bootstrap:main
4
+ sqlite-sh-diagram = sqlite_sh.diagram:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 adwedelf
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ sqlite_sh