SQLAlchemy Essential: Chapter 4
This commit is contained in:
@@ -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 <module>
|
||||
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 <module>
|
||||
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
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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" },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user