From ce686bf28cb72dbd4e17b654e9a33a96834fd5cf Mon Sep 17 00:00:00 2001 From: Pavel Vavilin Date: Fri, 18 Sep 2026 14:44:23 +0500 Subject: [PATCH] SQLAlchemy Essential: Chapter 4 --- Essential-SQLAlchemy-2nd.org | 907 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 + uv.lock | 105 +++- 3 files changed, 1003 insertions(+), 11 deletions(-) diff --git a/Essential-SQLAlchemy-2nd.org b/Essential-SQLAlchemy-2nd.org index 0ee4e85..d1b6e84 100644 --- a/Essential-SQLAlchemy-2nd.org +++ b/Essential-SQLAlchemy-2nd.org @@ -2266,3 +2266,910 @@ sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) CHECK constraint failed: except IntegrityError: logging.exception("") #+END_SRC + +#+RESULTS: +#+begin_example +2026-09-18 12:21:58,909 INFO sqlalchemy.engine.Engine BEGIN (implicit) +2026-09-18 12:21:58,910 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("cookies") +2026-09-18 12:21:58,910 INFO sqlalchemy.engine.Engine [raw sql] () +2026-09-18 12:21:58,910 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("cookies") +2026-09-18 12:21:58,910 INFO sqlalchemy.engine.Engine [raw sql] () +2026-09-18 12:21:58,910 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("users") +2026-09-18 12:21:58,910 INFO sqlalchemy.engine.Engine [raw sql] () +2026-09-18 12:21:58,910 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("users") +2026-09-18 12:21:58,910 INFO sqlalchemy.engine.Engine [raw sql] () +2026-09-18 12:21:58,910 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("orders") +2026-09-18 12:21:58,911 INFO sqlalchemy.engine.Engine [raw sql] () +2026-09-18 12:21:58,911 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("orders") +2026-09-18 12:21:58,911 INFO sqlalchemy.engine.Engine [raw sql] () +2026-09-18 12:21:58,911 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("line_items") +2026-09-18 12:21:58,911 INFO sqlalchemy.engine.Engine [raw sql] () +2026-09-18 12:21:58,911 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("line_items") +2026-09-18 12:21:58,911 INFO sqlalchemy.engine.Engine [raw sql] () +2026-09-18 12:21:58,912 INFO sqlalchemy.engine.Engine +CREATE TABLE cookies ( + cookie_id INTEGER NOT NULL, + cookie_name VARCHAR(50), + cookie_recipe_url VARCHAR(255), + cookie_sku VARCHAR(55), + quantity INTEGER, + unit_cost NUMERIC(12, 2), + PRIMARY KEY (cookie_id), + CONSTRAINT unit_cost_positive CHECK (unit_cost >= 0.00), + CONSTRAINT quantity_positive CHECK (quantity >= 0), + UNIQUE (cookie_recipe_url), + UNIQUE (cookie_sku) +) + + +2026-09-18 12:21:58,912 INFO sqlalchemy.engine.Engine [no key 0.00007s] () +2026-09-18 12:21:58,912 INFO sqlalchemy.engine.Engine CREATE INDEX ix_cookies_cookie_name ON cookies (cookie_name) +2026-09-18 12:21:58,912 INFO sqlalchemy.engine.Engine [no key 0.00006s] () +2026-09-18 12:21:58,913 INFO sqlalchemy.engine.Engine +CREATE TABLE users ( + user_id INTEGER NOT NULL, + username VARCHAR(15) NOT NULL, + email_address VARCHAR(255) NOT NULL, + phone VARCHAR(20) NOT NULL, + password VARCHAR(25) NOT NULL, + created_on DATETIME, + updated_on DATETIME, + CONSTRAINT user_pk PRIMARY KEY (user_id), + CONSTRAINT uix_username UNIQUE (username) +) + + +2026-09-18 12:21:58,913 INFO sqlalchemy.engine.Engine [no key 0.00006s] () +2026-09-18 12:21:58,913 INFO sqlalchemy.engine.Engine +CREATE TABLE orders ( + order_id INTEGER NOT NULL, + user_id INTEGER, + shipped BOOLEAN, + PRIMARY KEY (order_id), + FOREIGN KEY(user_id) REFERENCES users (user_id) +) + + +2026-09-18 12:21:58,913 INFO sqlalchemy.engine.Engine [no key 0.00005s] () +2026-09-18 12:21:58,913 INFO sqlalchemy.engine.Engine +CREATE TABLE line_items ( + line_items_id INTEGER NOT NULL, + order_id INTEGER, + cookie_id INTEGER, + quantity INTEGER, + extended_cost NUMERIC(12, 2), + PRIMARY KEY (line_items_id), + FOREIGN KEY(order_id) REFERENCES orders (order_id), + FOREIGN KEY(cookie_id) REFERENCES cookies (cookie_id) +) + + +2026-09-18 12:21:58,913 INFO sqlalchemy.engine.Engine [no key 0.00005s] () +2026-09-18 12:21:58,915 INFO sqlalchemy.engine.Engine INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (?, ?, ?, ?, ?, ?) +2026-09-18 12:21:58,915 INFO sqlalchemy.engine.Engine [generated in 0.00019s] ('cookiemon', 'mon@cookie.com', '111-111-1111', 'password', '2026-09-18 12:21:58.915122', '2026-09-18 12:21:58.915127') +2026-09-18 12:21:58,916 INFO sqlalchemy.engine.Engine SELECT users.username, users.password +FROM users +2026-09-18 12:21:58,916 INFO sqlalchemy.engine.Engine [generated in 0.00008s] () +2026-09-18 12:21:58,916 INFO sqlalchemy.engine.Engine SELECT users.username +FROM users +2026-09-18 12:21:58,916 INFO sqlalchemy.engine.Engine [generated in 0.00007s] () +[('cookiemon',)] +2026-09-18 12:21:58,916 INFO sqlalchemy.engine.Engine SAVEPOINT sa_savepoint_1 +2026-09-18 12:21:58,916 INFO sqlalchemy.engine.Engine [no key 0.00006s] () +2026-09-18 12:21:58,917 INFO sqlalchemy.engine.Engine INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (?, ?, ?, ?, ?, ?) +2026-09-18 12:21:58,917 INFO sqlalchemy.engine.Engine [cached since 0.002053s ago] ('cookiemon', 'damon@cookie.com', '111-111-1111', 'password', '2026-09-18 12:21:58.917037', '2026-09-18 12:21:58.917039') +2026-09-18 12:21:58,917 INFO sqlalchemy.engine.Engine ROLLBACK TO SAVEPOINT sa_savepoint_1 +2026-09-18 12:21:58,917 INFO sqlalchemy.engine.Engine [no key 0.00006s] () +ERROR:root:INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (:username, :email_address, :phone, :password, :created_on, :updated_on) +Traceback (most recent call last): + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context + self.dialect.do_execute( + ~~~~~~~~~~~~~~~~~~~~~~~^ + cursor, str_statement, effective_parameters, context + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ +sqlite3.IntegrityError: UNIQUE constraint failed: users.username + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/tmp/babel-sg4Ps9/python-ObJFq6", line 134, in + connection.execute(ins) + ~~~~~~~~~~~~~~~~~~^^^^^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1421, in execute + return meth( + self, + distilled_parameters, + execution_options or NO_OPTIONS, + ) + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection + return connection._execute_clauseelement( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + self, distilled_params, execution_options + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1643, in _execute_clauseelement + ret = self._execute_context( + dialect, + ...<8 lines>... + cache_hit=cache_hit, + ) + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context + return self._exec_single_context( + ~~~~~~~~~~~~~~~~~~~~~~~~~^ + dialect, context, statement, parameters + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context + self._handle_dbapi_exception( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + e, str_statement, effective_parameters, cursor, context + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception + raise sqlalchemy_exception.with_traceback(exc_info[2]) from e + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context + self.dialect.do_execute( + ~~~~~~~~~~~~~~~~~~~~~~~^ + cursor, str_statement, effective_parameters, context + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ +sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: users.username +[SQL: INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (?, ?, ?, ?, ?, ?)] +[parameters: ('cookiemon', 'damon@cookie.com', '111-111-1111', 'password', '2026-09-18 12:21:58.917037', '2026-09-18 12:21:58.917039')] +(Background on this error at: https://sqlalche.me/e/20/gkpj) +2026-09-18 12:21:58,921 INFO sqlalchemy.engine.Engine INSERT INTO cookies (cookie_name, cookie_recipe_url, cookie_sku, quantity, unit_cost) VALUES (?, ?, ?, ?, ?) +INFO:sqlalchemy.engine.Engine:INSERT INTO cookies (cookie_name, cookie_recipe_url, cookie_sku, quantity, unit_cost) VALUES (?, ?, ?, ?, ?) +2026-09-18 12:21:58,921 INFO sqlalchemy.engine.Engine [generated in 0.00022s] [('chocolate chip', 'http://some.aweso.me/cookie/recipe.html', 'CC01', '12', 0.5), ('dark chocolate chip', 'http://some.aweso.me/cookie/recipe_dark.html', 'CC02', '1', 0.75)] +INFO:sqlalchemy.engine.Engine:[generated in 0.00022s] [('chocolate chip', 'http://some.aweso.me/cookie/recipe.html', 'CC01', '12', 0.5), ('dark chocolate chip', 'http://some.aweso.me/cookie/recipe_dark.html', 'CC02', '1', 0.75)] +2026-09-18 12:21:58,922 INFO sqlalchemy.engine.Engine INSERT INTO orders (order_id, user_id, shipped) VALUES (?, ?, ?) +INFO:sqlalchemy.engine.Engine:INSERT INTO orders (order_id, user_id, shipped) VALUES (?, ?, ?) +2026-09-18 12:21:58,922 INFO sqlalchemy.engine.Engine [generated in 0.00015s] ('1', 1, 0) +INFO:sqlalchemy.engine.Engine:[generated in 0.00015s] ('1', 1, 0) +2026-09-18 12:21:58,922 INFO sqlalchemy.engine.Engine INSERT INTO line_items (order_id, cookie_id, quantity, extended_cost) VALUES (?, ?, ?, ?) +INFO:sqlalchemy.engine.Engine:INSERT INTO line_items (order_id, cookie_id, quantity, extended_cost) VALUES (?, ?, ?, ?) +2026-09-18 12:21:58,922 INFO sqlalchemy.engine.Engine [generated in 0.00014s] (1, 1, 9, 4.5) +INFO:sqlalchemy.engine.Engine:[generated in 0.00014s] (1, 1, 9, 4.5) +2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine INSERT INTO orders (order_id, user_id, shipped) VALUES (?, ?, ?) +INFO:sqlalchemy.engine.Engine:INSERT INTO orders (order_id, user_id, shipped) VALUES (?, ?, ?) +2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine [cached since 0.001143s ago] ('2', 1, 0) +INFO:sqlalchemy.engine.Engine:[cached since 0.001143s ago] ('2', 1, 0) +2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine INSERT INTO line_items (order_id, cookie_id, quantity, extended_cost) VALUES (?, ?, ?, ?) +INFO:sqlalchemy.engine.Engine:INSERT INTO line_items (order_id, cookie_id, quantity, extended_cost) VALUES (?, ?, ?, ?) +2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine [generated in 0.00011s] [(2, 1, 4, 1.5), (2, 2, 1, 4.5)] +INFO:sqlalchemy.engine.Engine:[generated in 0.00011s] [(2, 1, 4, 1.5), (2, 2, 1, 4.5)] +2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine COMMIT +INFO:sqlalchemy.engine.Engine:COMMIT +2026-09-18 12:21:58,924 INFO sqlalchemy.engine.Engine BEGIN (implicit) +INFO:sqlalchemy.engine.Engine:BEGIN (implicit) +2026-09-18 12:21:58,924 INFO sqlalchemy.engine.Engine SELECT line_items.cookie_id, line_items.quantity +FROM line_items +WHERE line_items.order_id = ? +INFO:sqlalchemy.engine.Engine:SELECT line_items.cookie_id, line_items.quantity +FROM line_items +WHERE line_items.order_id = ? +2026-09-18 12:21:58,924 INFO sqlalchemy.engine.Engine [generated in 0.00010s] (1,) +INFO:sqlalchemy.engine.Engine:[generated in 0.00010s] (1,) +2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ? +INFO:sqlalchemy.engine.Engine:UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ? +2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine [generated in 0.00009s] (9, 1) +INFO:sqlalchemy.engine.Engine:[generated in 0.00009s] (9, 1) +2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine UPDATE orders SET shipped=? WHERE orders.order_id = ? +INFO:sqlalchemy.engine.Engine:UPDATE orders SET shipped=? WHERE orders.order_id = ? +2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine [generated in 0.00012s] (1, 1) +INFO:sqlalchemy.engine.Engine:[generated in 0.00012s] (1, 1) +Shipped order ID: 1 +2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine COMMIT +INFO:sqlalchemy.engine.Engine:COMMIT +2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine BEGIN (implicit) +INFO:sqlalchemy.engine.Engine:BEGIN (implicit) +2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_name, cookies.quantity +FROM cookies +INFO:sqlalchemy.engine.Engine:SELECT cookies.cookie_name, cookies.quantity +FROM cookies +2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine [generated in 0.00012s] () +INFO:sqlalchemy.engine.Engine:[generated in 0.00012s] () +[('chocolate chip', 3), ('dark chocolate chip', 1)] +2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine ROLLBACK +INFO:sqlalchemy.engine.Engine:ROLLBACK +2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine BEGIN (implicit) +INFO:sqlalchemy.engine.Engine:BEGIN (implicit) +2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine SELECT line_items.cookie_id, line_items.quantity +FROM line_items +WHERE line_items.order_id = ? +INFO:sqlalchemy.engine.Engine:SELECT line_items.cookie_id, line_items.quantity +FROM line_items +WHERE line_items.order_id = ? +2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine [cached since 0.002892s ago] (2,) +INFO:sqlalchemy.engine.Engine:[cached since 0.002892s ago] (2,) +2026-09-18 12:21:58,928 INFO sqlalchemy.engine.Engine UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ? +INFO:sqlalchemy.engine.Engine:UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ? +2026-09-18 12:21:58,928 INFO sqlalchemy.engine.Engine [cached since 0.002047s ago] (4, 1) +INFO:sqlalchemy.engine.Engine:[cached since 0.002047s ago] (4, 1) +ERROR:root:Failed to ship order 2 +Traceback (most recent call last): + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context + self.dialect.do_execute( + ~~~~~~~~~~~~~~~~~~~~~~~^ + cursor, str_statement, effective_parameters, context + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ +sqlite3.IntegrityError: CHECK constraint failed: quantity_positive + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/tmp/babel-sg4Ps9/python-ObJFq6", line 224, in ship_it_atomic + conn.execute(u) + ~~~~~~~~~~~~^^^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1421, in execute + return meth( + self, + distilled_parameters, + execution_options or NO_OPTIONS, + ) + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection + return connection._execute_clauseelement( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + self, distilled_params, execution_options + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1643, in _execute_clauseelement + ret = self._execute_context( + dialect, + ...<8 lines>... + cache_hit=cache_hit, + ) + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context + return self._exec_single_context( + ~~~~~~~~~~~~~~~~~~~~~~~~~^ + dialect, context, statement, parameters + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context + self._handle_dbapi_exception( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + e, str_statement, effective_parameters, cursor, context + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception + raise sqlalchemy_exception.with_traceback(exc_info[2]) from e + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context + self.dialect.do_execute( + ~~~~~~~~~~~~~~~~~~~~~~~^ + cursor, str_statement, effective_parameters, context + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ +sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) CHECK constraint failed: quantity_positive +[SQL: UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ?] +[parameters: (4, 1)] +(Background on this error at: https://sqlalche.me/e/20/gkpj) +2026-09-18 12:21:58,930 INFO sqlalchemy.engine.Engine ROLLBACK +INFO:sqlalchemy.engine.Engine:ROLLBACK +ERROR:root: +Traceback (most recent call last): + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context + self.dialect.do_execute( + ~~~~~~~~~~~~~~~~~~~~~~~^ + cursor, str_statement, effective_parameters, context + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ +sqlite3.IntegrityError: CHECK constraint failed: quantity_positive + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/tmp/babel-sg4Ps9/python-ObJFq6", line 241, in + ship_it_atomic(2) + ~~~~~~~~~~~~~~^^^ + File "/tmp/babel-sg4Ps9/python-ObJFq6", line 224, in ship_it_atomic + conn.execute(u) + ~~~~~~~~~~~~^^^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1421, in execute + return meth( + self, + distilled_parameters, + execution_options or NO_OPTIONS, + ) + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection + return connection._execute_clauseelement( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + self, distilled_params, execution_options + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1643, in _execute_clauseelement + ret = self._execute_context( + dialect, + ...<8 lines>... + cache_hit=cache_hit, + ) + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context + return self._exec_single_context( + ~~~~~~~~~~~~~~~~~~~~~~~~~^ + dialect, context, statement, parameters + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context + self._handle_dbapi_exception( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + e, str_statement, effective_parameters, cursor, context + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception + raise sqlalchemy_exception.with_traceback(exc_info[2]) from e + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context + self.dialect.do_execute( + ~~~~~~~~~~~~~~~~~~~~~~~^ + cursor, str_statement, effective_parameters, context + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ +sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) CHECK constraint failed: quantity_positive +[SQL: UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ?] +[parameters: (4, 1)] +(Background on this error at: https://sqlalche.me/e/20/gkpj) +#+end_example +* Chapter 4: Testing +#+BEGIN_SRC python :tangle db.py :shebang "#!/usr/bin/env python3" :results none + from datetime import datetime + from sqlalchemy import ( + MetaData, + Table, + Column, + Integer, + Numeric, + String, + DateTime, + ForeignKey, + Boolean, + create_engine, + ) + from sqlalchemy.sql import insert + + + class DataAccessLayer: + connection = None + engine = None + conn_string = None + metadata = MetaData() + cookies = Table( + "cookies", + metadata, + Column("cookie_id", Integer(), primary_key=True), + Column("cookie_name", String(50), index=True), + Column("cookie_recipe_url", String(255)), + Column("cookie_sku", String(55)), + Column("quantity", Integer()), + Column("unit_cost", Numeric(12, 2)), + ) + + users = Table( + "users", + metadata, + Column("user_id", Integer(), primary_key=True), + Column("customer_number", Integer(), autoincrement=True), + Column("username", String(15), nullable=False, unique=True), + Column("email_address", String(255), nullable=False), + Column("phone", String(20), nullable=False), + Column("password", String(25), nullable=False), + Column("created_on", DateTime(), default=datetime.now), + Column("updated_on", DateTime(), default=datetime.now, onupdate=datetime.now), + ) + + orders = Table( + "orders", + metadata, + Column("order_id", Integer()), + Column("user_id", ForeignKey("users.user_id")), + Column("shipped", Boolean(), default=False), + ) + + line_items = Table( + "line_items", + metadata, + Column("line_items_id", Integer(), primary_key=True), + Column("order_id", ForeignKey("orders.order_id")), + Column("cookie_id", ForeignKey("cookies.cookie_id")), + Column("quantity", Integer()), + Column("extended_cost", Numeric(12, 2)), + ) + + def db_init(self, conn_string): + self.engine = create_engine(conn_string or self.conn_string) + self.metadata.create_all(self.engine) + self.connection = self.engine.connect() + + + dal = DataAccessLayer() + + + def prep_db(): + ins = dal.cookies.insert() + dal.connection.execute( + ins, + dict( + cookie_name="dark chocolate chip", + cookie_recipe_url="http://some.aweso.me/cookie/recipe_dark.html", + cookie_sku="CC02", + quantity="1", + unit_cost="0.75", + ), + ) + inventory_list = [ + { + "cookie_name": "peanut butter", + "cookie_recipe_url": "http://some.aweso.me/cookie/peanut.html", + "cookie_sku": "PB01", + "quantity": "24", + "unit_cost": "0.25", + }, + { + "cookie_name": "oatmeal raisin", + "cookie_recipe_url": "http://some.okay.me/cookie/raisin.html", + "cookie_sku": "EWW01", + "quantity": "100", + "unit_cost": "1.00", + }, + ] + dal.connection.execute(ins, inventory_list) + + customer_list = [ + { + "username": "cookiemon", + "email_address": "mon@cookie.com", + "phone": "111-111-1111", + "password": "password", + }, + { + "username": "cakeeater", + "email_address": "cakeeater@cake.com", + "phone": "222-222-2222", + "password": "password", + }, + { + "username": "pieguy", + "email_address": "guy@pie.com", + "phone": "333-333-3333", + "password": "password", + }, + ] + ins = dal.users.insert() + dal.connection.execute(ins, customer_list) + ins = insert(dal.orders).values(user_id=1, order_id="wlk001") + dal.connection.execute(ins) + ins = insert(dal.line_items) + order_items = [ + {"order_id": "wlk001", "cookie_id": 1, "quantity": 2, "extended_cost": 1.00}, + {"order_id": "wlk001", "cookie_id": 3, "quantity": 12, "extended_cost": 3.00}, + ] + dal.connection.execute(ins, order_items) + ins = insert(dal.orders).values(user_id=2, order_id="ol001") + dal.connection.execute(ins) + ins = insert(dal.line_items) + order_items = [ + {"order_id": "ol001", "cookie_id": 1, "quantity": 24, "extended_cost": 12.00}, + {"order_id": "ol001", "cookie_id": 4, "quantity": 6, "extended_cost": 6.00}, + ] + dal.connection.execute(ins, order_items) +#+END_SRC + +#+BEGIN_SRC python :tangle app.py :results none :shebang "#!/usr/bin/env python3" + from db import dal + from sqlalchemy.sql import select + + + def get_orders_by_customer( + cust_name: str, shipped: bool | None = None, details: bool = False + ): + columns = [dal.orders.c.order_id, dal.users.c.username, dal.users.c.phone] + joins = dal.users.join(dal.orders) + if details: + columns.extend( + [ + dal.cookies.c.cookie_name, + dal.line_items.c.quantity, + dal.line_items.c.extended_cost, + ] + ) + joins = joins.join(dal.line_items).join(dal.cookies) + cust_orders = select(*columns) + cust_orders = cust_orders.select_from(joins).where( + dal.users.c.username == cust_name + ) + if shipped is not None: + cust_orders = cust_orders.where(dal.orders.c.shipped == shipped) + return dal.connection.execute(cust_orders).fetchall() +#+END_SRC +*** UnitTest +#+BEGIN_SRC python :tangle test_app.py + import unittest + from decimal import Decimal + + from app import get_orders_by_customer + from db import dal, prep_db + + + class TestApp(unittest.TestCase): + cookie_orders = [("wlk001", "cookiemon", "111-111-1111")] + cookie_details = [ + ( + "wlk001", + "cookiemon", + "111-111-1111", + "dark chocolate chip", + 2, + Decimal("1.00"), + ), + ("wlk001", "cookiemon", "111-111-1111", "oatmeal raisin", 12, Decimal("3.00")), + ] + + @classmethod + def setUpClass(cls): + dal.db_init("sqlite:///:memory:") + prep_db() + + def test_orders_by_customer_blank(self): + """ + - значение cust_name пустая строка, + - shipped = None + """ + results = get_orders_by_customer("") + self.assertEqual(results, []) + + def test_orders_by_customer_blank_shipped(self): + """ + - значение cust_name пустая строка, + - shipped = True + """ + results = get_orders_by_customer("", True) + self.assertEqual(results, []) + + def test_orders_by_customer_blank_notshipped(self): + """ + - значение cust_name пустая строка, + - shipped = False + """ + results = get_orders_by_customer("", False) + self.assertEqual(results, []) + + def test_orders_by_customer_blank_details(self): + results = get_orders_by_customer("", details=True) + self.assertEqual(results, []) + + def test_orders_by_customer_blank_shipped_details(self): + results = get_orders_by_customer("", True, True) + self.assertEqual(results, []) + + def test_orders_by_customer_blank_notshipped_details(self): + results = get_orders_by_customer("", False, True) + self.assertEqual(results, []) + + def test_orders_by_customer(self): + expected_results = [("wlk001", "cookiemon", "111-111-1111")] + results = get_orders_by_customer("cookiemon") + self.assertEqual(results, expected_results) + + def test_orders_by_customer_shipped_only(self): + results = get_orders_by_customer("cookiemon", True) + self.assertEqual(results, []) + + def test_orders_by_customer_unshipped_only(self): + expected_results = [("wlk001", "cookiemon", "111-111-1111")] + results = get_orders_by_customer("cookiemon", False) + self.assertEqual(results, expected_results) + + def test_orders_by_customer_with_details(self): + expected_results = [ + ( + "wlk001", + "cookiemon", + "111-111-1111", + "dark chocolate chip", + 2, + Decimal("1.00"), + ), + ( + "wlk001", + "cookiemon", + "111-111-1111", + "oatmeal raisin", + 12, + Decimal("3.00"), + ), + ] + results = get_orders_by_customer("cookiemon", details=True) + self.assertEqual(results, expected_results) + + def test_orders_by_customer_shipped_only_with_details(self): + results = get_orders_by_customer("cookiemon", True, True) + self.assertEqual(results, []) + + def test_orders_by_customer_unshipped_only_details(self): + expected_results = [ + ( + "wlk001", + "cookiemon", + "111-111-1111", + "dark chocolate chip", + 2, + Decimal("1.00"), + ), + ( + "wlk001", + "cookiemon", + "111-111-1111", + "oatmeal raisin", + 12, + Decimal("3.00"), + ), + ] + results = get_orders_by_customer("cookiemon", False, True) + self.assertEqual(results, expected_results) +#+END_SRC +#+BEGIN_SRC python :tangle test_app_mocked.py :results none + import unittest + from decimal import Decimal + from unittest import mock + + from app import get_orders_by_customer + from db import dal, prep_db + + + class TestApp(unittest.TestCase): + cookie_orders = [("wlk001", "cookiemon", "111-111-1111")] + cookie_details = [ + ( + "wlk001", + "cookiemon", + "111-111-1111", + "dark chocolate chip", + 2, + Decimal("1.00"), + ), + ("wlk001", "cookiemon", "111-111-1111", "oatmeal raisin", 12, Decimal("3.00")), + ] + + @mock.patch("app.select") + @mock.patch("app.dal.connection") + def test_orders_by_customer_blank(self, mock_conn, mock_select): + mock_select.return_value.select_from.return_value.where.return_value = None + mock_conn.execute.return_value.fetchall.return_value = [] + results = get_orders_by_customer("") + self.assertEqual(results, []) + + @mock.patch("app.dal.connection") + def test_orders_by_customer_blank_shipped(self, mock_conn): + mock_conn.execute.return_value.fetchall.return_value = [] + results = get_orders_by_customer("", True) + self.assertEqual(results, []) + + @mock.patch("app.dal.connection") + def test_orders_by_customer(self, mock_conn): + mock_conn.execute.return_value.fetchall.return_value = self.cookie_orders + results = get_orders_by_customer("cookiemon") + self.assertEqual(results, self.cookie_orders) +#+END_SRC + +*** pytest +#+BEGIN_SRC python :tangle test_app_pytest.py :results none + from decimal import Decimal + + import pytest + + from app import get_orders_by_customer + from db import dal, prep_db + + + @pytest.fixture(autouse=True, scope="class") + def setup_db(): + dal.db_init("sqlite:///:memory:") + prep_db() + yield + # здесь можно закрыть соединение, если нужно: + # dal.connection.close() + + + def test_orders_by_customer_blank(): + """ + - значение cust_name пустая строка, + - shipped = None + """ + results = get_orders_by_customer("") + assert results == [] + + + def test_orders_by_customer_blank_shipped(): + """ + - значение cust_name пустая строка, + - shipped = True + """ + results = get_orders_by_customer("", True) + assert results == [] + + + def test_orders_by_customer_blank_notshipped(): + """ + - значение cust_name пустая строка, + - shipped = False + """ + results = get_orders_by_customer("", False) + assert results == [] + + + def test_orders_by_customer_blank_details(): + results = get_orders_by_customer("", details=True) + assert results == [] + + + def test_orders_by_customer_blank_shipped_details(): + results = get_orders_by_customer("", True, True) + assert results == [] + + + def test_orders_by_customer_blank_notshipped_details(): + results = get_orders_by_customer("", False, True) + assert results == [] + + + def test_orders_by_customer(): + expected_results = [("wlk001", "cookiemon", "111-111-1111")] + results = get_orders_by_customer("cookiemon") + assert results == expected_results + + + def test_orders_by_customer_shipped_only(): + results = get_orders_by_customer("cookiemon", True) + assert results == [] + + + def test_orders_by_customer_unshipped_only(): + expected_results = [("wlk001", "cookiemon", "111-111-1111")] + results = get_orders_by_customer("cookiemon", False) + assert results == expected_results + + + def test_orders_by_customer_with_details(): + expected_results = [ + ( + "wlk001", + "cookiemon", + "111-111-1111", + "dark chocolate chip", + 2, + Decimal("1.00"), + ), + ( + "wlk001", + "cookiemon", + "111-111-1111", + "oatmeal raisin", + 12, + Decimal("3.00"), + ), + ] + results = get_orders_by_customer("cookiemon", details=True) + assert results == expected_results + + + def test_orders_by_customer_shipped_only_with_details(): + results = get_orders_by_customer("cookiemon", True, True) + assert results == [] + + + def test_orders_by_customer_unshipped_only_details(): + expected_results = [ + ( + "wlk001", + "cookiemon", + "111-111-1111", + "dark chocolate chip", + 2, + Decimal("1.00"), + ), + ( + "wlk001", + "cookiemon", + "111-111-1111", + "oatmeal raisin", + 12, + Decimal("3.00"), + ), + ] + results = get_orders_by_customer("cookiemon", False, True) + assert results == expected_results +#+END_SRC + +#+BEGIN_SRC python :tangle test_app_mocked_pytest.py :results none + from decimal import Decimal + from unittest import mock + + import pytest + + from app import get_orders_by_customer + from db import dal, prep_db + + + # --- общие тестовые данные --- + + @pytest.fixture + def cookie_orders(): + return [("wlk001", "cookiemon", "111-111-1111")] + + + @pytest.fixture + def cookie_details(): + return [ + ( + "wlk001", + "cookiemon", + "111-111-1111", + "dark chocolate chip", + 2, + Decimal("1.00"), + ), + ("wlk001", "cookiemon", "111-111-1111", "oatmeal raisin", 12, Decimal("3.00")), + ] + + + # --- тесты --- + + @mock.patch("app.select") + @mock.patch("app.dal.connection") + def test_orders_by_customer_blank(mock_conn, mock_select): + mock_select.return_value.select_from.return_value.where.return_value = None + mock_conn.execute.return_value.fetchall.return_value = [] + + results = get_orders_by_customer("") + + assert results == [] + + + @mock.patch("app.dal.connection") + def test_orders_by_customer_blank_shipped(mock_conn): + mock_conn.execute.return_value.fetchall.return_value = [] + + results = get_orders_by_customer("", True) + + assert results == [] + + + @mock.patch("app.dal.connection") + def test_orders_by_customer(mock_conn, cookie_orders): + mock_conn.execute.return_value.fetchall.return_value = cookie_orders + + results = get_orders_by_customer("cookiemon") + + assert results == cookie_orders +#+END_SRC diff --git a/pyproject.toml b/pyproject.toml index f39ae43..bcd1f83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,8 @@ readme = "README.md" requires-python = ">=3.14" dependencies = [ "alembic>=1.20.0", + "pytest>=9.1.1", + "pytest-randomly>=5.0.0", "sqlalchemy>=2.0.52", "sqlparse>=0.6.0", ] diff --git a/uv.lock b/uv.lock index e117a0e..ad9ac25 100644 --- a/uv.lock +++ b/uv.lock @@ -16,6 +16,15 @@ wheels = [ { url = "https://mirror.yandex.ru/pypi/web/packages/3f/27/78a89b55b0904d222183164e079b4ca56208e94eff1d35ad1f1ad5be9b06/alembic-1.20.0-py3-none-any.whl", hash = "sha256:77eb101048d95f982c0353e9233404889dcd7a6fc244c107836c0e2fc9cf7d9d" }, ] +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://mirror.yandex.ru/pypi/web/simple/" } +sdist = { url = "https://mirror.yandex.ru/pypi/web/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } +wheels = [ + { url = "https://mirror.yandex.ru/pypi/web/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, +] + [[package]] name = "greenlet" version = "3.5.5" @@ -55,6 +64,15 @@ wheels = [ { url = "https://mirror.yandex.ru/pypi/web/packages/df/af/419a4e383bd600858a9b67e9b280a60fdc383ee3f2fe5b6c0c1ef04e74d1/greenlet-3.5.5-cp315-cp315t-win_arm64.whl", hash = "sha256:7f049911ee81a16a03c33d5450d8d5867d27f596ca5fb201b86f4524e874468b" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://mirror.yandex.ru/pypi/web/simple/" } +sdist = { url = "https://mirror.yandex.ru/pypi/web/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730" } +wheels = [ + { url = "https://mirror.yandex.ru/pypi/web/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" }, +] + [[package]] name = "mako" version = "1.4.1" @@ -97,25 +115,86 @@ wheels = [ { url = "https://mirror.yandex.ru/pypi/web/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa" }, ] +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://mirror.yandex.ru/pypi/web/simple/" } +sdist = { url = "https://mirror.yandex.ru/pypi/web/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79" } +wheels = [ + { url = "https://mirror.yandex.ru/pypi/web/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://mirror.yandex.ru/pypi/web/simple/" } +sdist = { url = "https://mirror.yandex.ru/pypi/web/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3" } +wheels = [ + { url = "https://mirror.yandex.ru/pypi/web/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://mirror.yandex.ru/pypi/web/simple/" } +sdist = { url = "https://mirror.yandex.ru/pypi/web/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c" } +wheels = [ + { url = "https://mirror.yandex.ru/pypi/web/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://mirror.yandex.ru/pypi/web/simple/" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://mirror.yandex.ru/pypi/web/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313" } +wheels = [ + { url = "https://mirror.yandex.ru/pypi/web/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c" }, +] + +[[package]] +name = "pytest-randomly" +version = "5.0.0" +source = { registry = "https://mirror.yandex.ru/pypi/web/simple/" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://mirror.yandex.ru/pypi/web/packages/01/3b/6a40e1b9d925651e601e056a97f60d8a1daeddeac03d5609be60cb4362ce/pytest_randomly-5.0.0.tar.gz", hash = "sha256:e9c575a5873ef168ddbe340ed9e97ce9edb4492ccc821e4b2ac6bb1f0ed515d2" } +wheels = [ + { url = "https://mirror.yandex.ru/pypi/web/packages/10/b4/47e939285caad9a623d021512912ac08dc92a467ad075d179f43729d2934/pytest_randomly-5.0.0-py3-none-any.whl", hash = "sha256:8a0d4703115c0c25b38b6e129fc16b1947b9643ff26a41bc1d185d7e5a7689c1" }, +] + [[package]] name = "sqlalchemy" -version = "2.0.53" +version = "2.0.54" source = { registry = "https://mirror.yandex.ru/pypi/web/simple/" } dependencies = [ { 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'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://mirror.yandex.ru/pypi/web/packages/4c/de/59fc3d2e63dfd367e803417c40e70e35b6c0a0997420cb082d351cdd052b/sqlalchemy-2.0.53.tar.gz", hash = "sha256:b568a8ac0778988c2710476017ff74fb5dcb9b9322d757189c3d7fc4cc8026fa" } +sdist = { url = "https://mirror.yandex.ru/pypi/web/packages/29/9c/271aa905cf2964f841371a97f3e63ab692bf51b4423d0491e67bc7f64037/sqlalchemy-2.0.54.tar.gz", hash = "sha256:baa8521e8ee9f24e75dfc7aaabc08020e551ef0d48d7c3e3536f5cddf277586b" } wheels = [ - { url = "https://mirror.yandex.ru/pypi/web/packages/bf/c4/164f0b33493a694a525e174d1ff978264167315d85f5128657d67522932a/sqlalchemy-2.0.53-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bc02d73633f5cee4ebecfc6c20b09a28bdfd940cc2ccf5bf119a6a7de6b5a3a3" }, - { url = "https://mirror.yandex.ru/pypi/web/packages/bf/85/15503b7c81567602a88a45ae14ecaef2f9372bf82e4cd2e3c7588e8cdea5/sqlalchemy-2.0.53-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b06e32d92ed898240793982820c46ae22bdec4f24dcb4ae4a6859001674db186" }, - { url = "https://mirror.yandex.ru/pypi/web/packages/fe/bb/a35e0ccb4f1d6f37b7b9d8810de787cbd429c427082e77e0ecdedf99b313/sqlalchemy-2.0.53-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03dfba40d1de2da62370d5e85ba13ffb8e75f16e8af9e93170a9cf4f394636a5" }, - { url = "https://mirror.yandex.ru/pypi/web/packages/40/06/adb43aa08a16e8abf55148a2efd17a66291a217c461455d7b4de053fd092/sqlalchemy-2.0.53-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:659daf25d61973ad6dff7bbfe9fccd116da2ab27618f57c3b9bfb97cb02000f5" }, - { url = "https://mirror.yandex.ru/pypi/web/packages/ee/07/15eaf0ae3573a9fb3ad0e5437f8cb36e03e415839ba6e77c645f555fb0b2/sqlalchemy-2.0.53-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:577195de5eccba4fea6c289e7537e4d309d9767d16b31afe82deda1f241feb80" }, - { url = "https://mirror.yandex.ru/pypi/web/packages/3a/c7/b5c078b0d36adb22cee1aca21e745e20e69705357c1f0dae2d8d728360fa/sqlalchemy-2.0.53-cp314-cp314-win32.whl", hash = "sha256:eeb39947f8d3a0531afdcb422f659e6ac09653b50813dbedc9baf84ce2358320" }, - { url = "https://mirror.yandex.ru/pypi/web/packages/59/4d/81b6bf1eaa969fc74b54104f6059d08f52383416654e8149b55ad168f2fb/sqlalchemy-2.0.53-cp314-cp314-win_amd64.whl", hash = "sha256:414a71d25b18cd3d1d5ab18b512a550a34398dcfbd68336adff5cd1e93fc7f0d" }, - { url = "https://mirror.yandex.ru/pypi/web/packages/eb/27/06028ccab7e03887c68c5031a8e4e7bfc85f30c87449c79dcba49e7ccd55/sqlalchemy-2.0.53-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41548248e8a5897d3a5f67ad8c1edb78efe72ec00dcc985b5c19b7850949d039" }, - { url = "https://mirror.yandex.ru/pypi/web/packages/fc/3c/6ee5fe746b0820335d30c5172527e9a2c7409ac632376d15fded38ff715c/sqlalchemy-2.0.53-py3-none-any.whl", hash = "sha256:87eb05fc61a0835dfe1cbf135d0f99cb2764ad09350d92fdd3fe1b827a557d65" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/ab/c0/4a6503c9d22d6d00a5631082ab1484222ecf7d573db791e0f53161bf7745/sqlalchemy-2.0.54-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:abd6b21bc58e91c1932eb5d6d7f1bd44a551dfec7b6a7f517c3638ccd67233a0" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/12/28/f4424f618bd1f373761a32a821d53ce2c257350e9894bae9b968cb03d8fd/sqlalchemy-2.0.54-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5417322b3c025dd82918725d3bf09ec105fac95efc195722b8b06e1d9c381139" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/59/d2/7f0c77f8e042cb5f28275fea29c3080b4ac6fd4b3fdd7f59ff1ef3e28c11/sqlalchemy-2.0.54-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f84099e4b04a5c2d44500a2a8302eee5af4bc6fee63e8c6e9cf6786e747280e" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/af/32/3eaa930bcf71d17a72587081d2706a5fb97ab3f11a7e0fb838f581f7cff1/sqlalchemy-2.0.54-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a0956dc754d3884da7fe60097110ec7a8a105d26afa2f0844468f4b1598c6912" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/eb/cc/cddb6cbd4408e5c55b3bf722be26b9d3b54d42509f901b7ecc15debd3d1f/sqlalchemy-2.0.54-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:87ba8834318b0d8dc94fc6f405d071b5c08be32a6c3fd68107fd6952ee949615" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/34/2f/9c2aa5efc642b7f3b985d13565cd1a5e78856e079ef3022796fea5180498/sqlalchemy-2.0.54-cp314-cp314-win32.whl", hash = "sha256:842540e4382472f23c79589995752648d14696a8200d0807ed8c5c59c92ade44" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/e2/0b/3594f1f51769feb3022d686135dc5d8682a12345ed15ae61d0c0ca42cbee/sqlalchemy-2.0.54-cp314-cp314-win_amd64.whl", hash = "sha256:f4e8f955d13af83fb4e35c3472e5377ee22d3445eada1e5e48199588edb69835" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/c3/a5/c211a9a7af83222509519407e16a4db760c6df3d03be69ebc5414d465321/sqlalchemy-2.0.54-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ca05f4e7852cf48083b0cf157e4f9504b7068780422a50fa82f45353b8c5e14a" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/cb/2e/490ad7b3731116cb48ba170f7722eaa99a89707193e54389ca84b7ad55af/sqlalchemy-2.0.54-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a8b6417cbb7b735cf91c2b59453c2a554cefa0a8d7bd15aa35740739410d77" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/eb/25/15dfe6814847eeda773bd58ab6cf42a94b0176e5cf25a578fc1165777160/sqlalchemy-2.0.54-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e55a0b96a1577a1e108c91ccdeeb9cd92768f28ce206597311c3bf6d6423abd" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/aa/19/724d0a6a2fb2a86ff2d6008e581c258f722d9b7d8e52adc7b79085febdd4/sqlalchemy-2.0.54-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:69cab115c40fd02c5a22c68e4ee630fa6ef9a1650f1de944419aab1f7096fc4f" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/49/bb/9df1bd81c2f2d000cf5e7a1b1a9b331468a3aab939ad983355e701fa42b2/sqlalchemy-2.0.54-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e08397c6c42f53b2488acde9108b8bfefd52d7afd1bf2f03d2ffcab7a204aceb" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/df/c0/b5775465d3b89061d7c46057c31c56ff8fb6c509550b2b0c6570ffc248b3/sqlalchemy-2.0.54-cp314-cp314t-win32.whl", hash = "sha256:b9086b8ad48280ef6a7ba68262d5e44f7db1c4cb1973e8cdae8a9f467ae66f51" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/77/f8/296c2e46b4ccd3f29b00b954ef2f195dde32f98e352ed21de1d292cedc0d/sqlalchemy-2.0.54-cp314-cp314t-win_amd64.whl", hash = "sha256:b67c1744e453af833667fc1b84de07adb4a64f3536ef52a8ec5ac2b941d43970" }, + { url = "https://mirror.yandex.ru/pypi/web/packages/24/a1/bd5e3e99bc9c8863b51ac5b9b03008a7f2da8c6b59695992f5c654e1265b/sqlalchemy-2.0.54-py3-none-any.whl", hash = "sha256:7e33a631ab1474f8fe6b910bd1a07b7b8009c4c78cdd3fb18001b03e3bc2e1d2" }, ] [[package]] @@ -124,6 +203,8 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "alembic" }, + { name = "pytest" }, + { name = "pytest-randomly" }, { name = "sqlalchemy" }, { name = "sqlparse" }, ] @@ -131,6 +212,8 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "alembic", specifier = ">=1.20.0" }, + { name = "pytest", specifier = ">=9.1.1" }, + { name = "pytest-randomly", specifier = ">=5.0.0" }, { name = "sqlalchemy", specifier = ">=2.0.52" }, { name = "sqlparse", specifier = ">=0.6.0" }, ]