#+OPTIONS: H:3 num:t toc:t \n:nil @:t ::t |:t ^:{} _:{} -:t f:t *:t <:t todo:t #+KEYWORDS: sqlalchemy alembic orm #+PROPERTY: header-args :eval never-export :exports both :noweb yes #+INFOJS_OPT: view:t toc:t ltoc:t mouse:underline buttons:0 path:org-info.js #+HTML_HEAD: #+HTML_LINK_HOME: https://pimiento.github.io/ #+HTML_LINK_UP: https://pimiento.github.io/ #+TITLE: Essential SQLAlchemy (v1/v2) * Chapter 1: Schema and Types ** SQLAlchemy v1 #+BEGIN_SRC python :results output import sqlalchemy print(sqlalchemy.__version__) #+END_SRC #+RESULTS: : 2.0.52 #+NAME: A1 #+BEGIN_SRC python :session ch1 :results none from sqlalchemy import MetaData metadata = MetaData() #+END_SRC #+NAME: B1 #+BEGIN_SRC python :session ch1 :results none from sqlalchemy import Table, Column, Integer, Numeric, String, CheckConstraint cookies = Table( "cookies", metadata, Column("cookie_id", Integer(), primary_key=True), Column("cookie_name", String(50), index=True), Column("cookie_recipie_url", String(255)), Column("cookie_sku", String(55)), Column("quantity", Integer()), Column("unit_cost", Numeric(12, 2)), CheckConstraint("unit_cost >= 0.00", name="unit_cost_positive"), ) #+END_SRC #+NAME: C1 #+BEGIN_SRC python :session ch1 :results none from datetime import datetime from sqlalchemy import DateTime, PrimaryKeyConstraint, UniqueConstraint users = Table( "users", metadata, Column("user_id", Integer()), Column("username", String(15), nullable=False), 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), PrimaryKeyConstraint("user_id", name="user_pk"), UniqueConstraint("username", name="uix_username"), ) #+END_SRC #+BEGIN_SRC plantuml :file er.png @startuml skinparam linetype ortho skinparam nodesep 30 skinparam ranksep 40 skinparam entity { BackgroundColor White BorderColor Black BorderThickness 1.2 FontStyle bold FontSize 12 } entity orders { ,**order_id** : PK -- user_id : FK } entity line_items { ,**line_item_id**: PK -- ,**order_id** : FK ,**cookie_id** : FK quantity extended_cost } entity cookies { ,**cookie_id** : PK -- cookie_name cookie_reciepe_url cookie_sku quantity unit_cost } entity users { ,**user_id** : PK -- customer_number username email-address phone password created_on updated_on } orders ||--|{ line_items line_items }o--|| cookies orders }o--|| users orders -[hidden]right- line_items line_items -[hidden]right- cookies orders -[hidden]down- users @enduml #+END_SRC #+RESULTS: [[file:er.png]] #+NAME: D1 #+BEGIN_SRC python :session ch1 :results none from sqlalchemy import ForeignKey, Boolean orders = Table( "orders", metadata, Column("order_id", Integer(), primary_key=True), 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)), ) #+END_SRC #+NAME: E1 #+BEGIN_SRC python :session ch1 :results none :tangle ch1-v1.py :shebang "#!/usr/bin/env python3" <> <> <> <> from sqlalchemy import create_engine engine = create_engine("sqlite:///:memory:", echo=True) metadata.create_all(engine) #+END_SRC ** SQLAlchemy v2 #+BEGIN_SRC python :results none :tangle ch1-v2.py :shebang "#!/usr/bin/env python3" #!/usr/bin/env python3 from datetime import datetime from decimal import Decimal from sqlalchemy import ( Boolean, CheckConstraint, DateTime, ForeignKey, Integer, Numeric, String, UniqueConstraint, create_engine, ) from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): """Базовый класс для всех ORM-моделей.""" class Cookie(Base): __tablename__ = "cookies" cookie_id: Mapped[int] = mapped_column(Integer, primary_key=True) cookie_name: Mapped[str | None] = mapped_column(String(50), index=True) cookie_recipie_url: Mapped[str | None] = mapped_column(String(255)) cookie_sku: Mapped[str | None] = mapped_column(String(55)) quantity: Mapped[int | None] = mapped_column(Integer) unit_cost: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) __table_args__ = (CheckConstraint("unit_cost >= 0.00", name="unit_cost_positive"),) # Связь: одна cookie -> много line_items line_items: Mapped[list["LineItem"]] = relationship(back_populates="cookie") def __repr__(self) -> str: return f"Cookie(cookie_id={self.cookie_id!r}, cookie_name={self.cookie_name!r})" class User(Base): __tablename__ = "users" user_id: Mapped[int] = mapped_column(Integer, primary_key=True) username: Mapped[str] = mapped_column(String(15), nullable=False) email_address: Mapped[str] = mapped_column(String(255), nullable=False) phone: Mapped[str] = mapped_column(String(20), nullable=False) password: Mapped[str] = mapped_column(String(25), nullable=False) created_on: Mapped[datetime | None] = mapped_column(DateTime, default=datetime.now) updated_on: Mapped[datetime | None] = mapped_column( DateTime, default=datetime.now, onupdate=datetime.now ) __table_args__ = (UniqueConstraint("username", name="uix_username"),) # Связь: один user -> много orders orders: Mapped[list["Order"]] = relationship(back_populates="user") def __repr__(self) -> str: return f"User(user_id={self.user_id!r}, username={self.username!r})" class Order(Base): __tablename__ = "orders" order_id: Mapped[int] = mapped_column(Integer, primary_key=True) user_id: Mapped[int | None] = mapped_column(ForeignKey("users.user_id")) shipped: Mapped[bool | None] = mapped_column(Boolean, default=False) # Связи user: Mapped["User | None"] = relationship(back_populates="orders") line_items: Mapped[list["LineItem"]] = relationship(back_populates="order") def __repr__(self) -> str: return f"Order(order_id={self.order_id!r}, user_id={self.user_id!r})" class LineItem(Base): __tablename__ = "line_items" line_items_id: Mapped[int] = mapped_column(Integer, primary_key=True) order_id: Mapped[int | None] = mapped_column(ForeignKey("orders.order_id")) cookie_id: Mapped[int | None] = mapped_column(ForeignKey("cookies.cookie_id")) quantity: Mapped[int | None] = mapped_column(Integer) extended_cost: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) # Связи order: Mapped["Order | None"] = relationship(back_populates="line_items") cookie: Mapped["Cookie | None"] = relationship(back_populates="line_items") def __repr__(self) -> str: return ( f"LineItem(line_items_id={self.line_items_id!r}, " f"order_id={self.order_id!r}, cookie_id={self.cookie_id!r})" ) if __name__ == "__main__": engine = create_engine("sqlite:///:memory:", echo=True) Base.metadata.create_all(engine) #+END_SRC * Chapter 2: Working with Data via SQLAlchemy Core ** SQLAlchemy v1 :PROPERTIES: :header-args: :cache yes :header-args:python: :tangle ch2-v1.py :header-args: :session *ch2* :END: #+BEGIN_SRC python :results none :shebang "#!/usr/bin/env python3" from datetime import datetime from sqlalchemy import ( Boolean, CheckConstraint, Column, DateTime, ForeignKey, Integer, MetaData, Numeric, PrimaryKeyConstraint, String, Table, UniqueConstraint, create_engine, ) from sqlparse import format as sqlformat def sqlf( sql: str | object, reindent=True, keyword_case="upper", indent_width=2, strip_comments=False, identifier_case="lower", ): if not isinstance(sql, str): sql = str(sql) return sqlformat( sql, encoding="utf8", reindent=reindent, keyword_case=keyword_case, indent_width=indent_width, strip_comments=strip_comments, identifier_case=identifier_case, ) 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), unique=True), Column("cookie_sku", String(55), unique=True), Column("quantity", Integer()), Column("unit_cost", Numeric(12, 2)), CheckConstraint("unit_cost >= 0.00", name="unit_cost_positive"), ) users = Table( "users", metadata, Column("user_id", Integer()), Column("username", String(15), nullable=False), 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), PrimaryKeyConstraint("user_id", name="user_pk"), UniqueConstraint("username", name="uix_username"), ) orders = Table( "orders", metadata, Column("order_id", Integer(), primary_key=True), 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)), ) engine = create_engine("sqlite:///:memory:", echo=True) connection = engine.connect() metadata.create_all(engine) #+END_SRC *** Insert Data #+BEGIN_SRC python :results value ins = cookies.insert().values( cookie_name="chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe.html", cookie_sku="CC01", quantity="12", unit_cost="0.50" ) sqlf(ins) #+END_SRC #+RESULTS: A2 : INSERT INTO cookies (cookie_name, cookie_recipe_url, cookie_sku, quantity, unit_cost) : VALUES (:cookie_name, :cookie_recipe_url, :cookie_sku, :quantity, :unit_cost) #+BEGIN_SRC python :results value import json json.dumps(ins.compile().params, indent=2) #+END_SRC #+RESULTS[6c84e4959502481d55c0eede700458833e44cb09]: B2 : { : "cookie_name": "chocolate chip", : "cookie_recipe_url": "http://some.aweso.me/cookie/recipe.html", : "cookie_sku": "CC01", : "quantity": "12", : "unit_cost": "0.50" : } #+BEGIN_SRC python :results table result = connection.execute(ins) result.inserted_primary_key #+END_SRC #+RESULTS[2f53d5414462ab280cdac9899df147cdfb23ca85]: C2 | 1 | #+BEGIN_SRC python :results table ins = cookies.insert() result = 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, ), ) result.inserted_primary_key #+END_SRC #+RESULTS[ce29752b9e0ffb2e8359d9455b40c0177eaed45b]: D2 | 2 | #+BEGIN_SRC python :results table from sqlalchemy import insert ins = insert(cookies).values( cookie_name="ligth chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe_light.html", cookie_sku="CC03", quantity=10, unit_cost=0.5 ) result = connection.execute(ins) result.inserted_primary_key #+END_SRC #+RESULTS[27f2d7147460a4ef494532268ad9635b7b554f06]: E2 | 3 | #+BEGIN_SRC python :results table ins = cookies.insert() inventory_list = [ { "cookie_name": "peanut butter", "cookie_recipe_url": "http://some.aweso.me/cookie/panut.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 } ] result = connection.execute(ins, inventory_list) result.inserted_primary_key_rows #+END_SRC #+RESULTS[4a8500d49f6c26986acb280384da05aba54bef5c]: | None | | None | *** Querying Data #+BEGIN_SRC python :results table from sqlalchemy.sql import select s = select(cookies) # ResultProxy rp = connection.execute(s) rp.fetchall() #+END_SRC #+RESULTS[e34d92c2943fc699b5606f0b46be7beb21db1388]: | 1 | chocolate chip | http://some.aweso.me/cookie/recipe.html | CC01 | 12 | Decimal | (0.50) | | 2 | dark chocolate chip | http://some.aweso.me/cookie/recipe_dark.html | CC02 | 1 | Decimal | (0.75) | | 3 | ligth chocolate chip | http://some.aweso.me/cookie/recipe_light.html | CC03 | 10 | Decimal | (0.50) | | 4 | peanut butter | http://some.aweso.me/cookie/panut.html | PB01 | 24 | Decimal | (0.25) | | 5 | oatmeal raisin | http://some.okay.me/cookie/raisin.html | EWW01 | 100 | Decimal | (1.00) | #+BEGIN_SRC python :results table s = cookies.select() rp = connection.execute(s) results = rp.fetchall() results #+END_SRC #+RESULTS[2800f4ee31f49d7d6b362d7ee7093f65108d076a]: | 1 | chocolate chip | http://some.aweso.me/cookie/recipe.html | CC01 | 12 | Decimal | (0.50) | | 2 | dark chocolate chip | http://some.aweso.me/cookie/recipe_dark.html | CC02 | 1 | Decimal | (0.75) | | 3 | ligth chocolate chip | http://some.aweso.me/cookie/recipe_light.html | CC03 | 10 | Decimal | (0.50) | | 4 | peanut butter | http://some.aweso.me/cookie/panut.html | PB01 | 24 | Decimal | (0.25) | | 5 | oatmeal raisin | http://some.okay.me/cookie/raisin.html | EWW01 | 100 | Decimal | (1.00) | **** ResultProxy #+BEGIN_SRC python :results table first_row = results[0] ( first_row[1], first_row.cookie_name, # так не работает в SQLAlchemy 2 #first_row[cookies.c.cookie_name], # так работает в SQLAlchemy 2 first_row._mapping[cookies.c.cookie_name] ) #+END_SRC #+RESULTS[0e4b28e683fb34c9cf927a4ee33749f0c86a4e79]: | chocolate chip | chocolate chip | chocolate chip | #+BEGIN_SRC python :results output rp = connection.execute(s) for record in rp: print(record.cookie_name) #+END_SRC #+RESULTS[111aa8e58f95f1c58373419ef284b39f24928b09]: : 2026-09-16 18:20:06,397 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_id, cookies.cookie_name, cookies.cookie_recipe_url, cookies.cookie_sku, cookies.quantity, cookies.unit_cost : FROM cookies : 2026-09-16 18:20:06,398 INFO sqlalchemy.engine.Engine [cached since 0.1297s ago] () : chocolate chip : dark chocolate chip : ligth chocolate chip : peanut butter : oatmeal raisin **** Controlling the Columns in the Query #+BEGIN_SRC python :results output s = select(cookies.c.cookie_name, cookies.c.quantity) rp = connection.execute(s) print(rp.keys()) result = rp.first() print(result) #+END_SRC #+RESULTS[d36e227d4963ac6498d81bc68b7230f0482aee36]: : 2026-09-16 18:20:06,420 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_name, cookies.quantity : FROM cookies : 2026-09-16 18:20:06,421 INFO sqlalchemy.engine.Engine [generated in 0.00045s] () : RMKeyView(['cookie_name', 'quantity']) : ('chocolate chip', 12) **** Ordering #+BEGIN_SRC python :results output s = select(cookies.c.cookie_name, cookies.c.quantity) s = s.order_by(cookies.c.quantity) rp = connection.execute(s) for cookie in rp: print("{} - {}".format(cookie.quantity, cookie.cookie_name)) #+END_SRC #+RESULTS[1a2648ac7db3983f3b128e81e12e34b1e0e3511b]: : 2026-09-16 18:20:06,443 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_name, cookies.quantity : FROM cookies ORDER BY cookies.quantity : 2026-09-16 18:20:06,443 INFO sqlalchemy.engine.Engine [generated in 0.00017s] () : 1 - dark chocolate chip : 10 - ligth chocolate chip : 12 - chocolate chip : 24 - peanut butter : 100 - oatmeal raisin #+BEGIN_SRC python :results table from sqlalchemy import desc s = select(cookies.c.cookie_name, cookies.c.quantity) s = s.order_by(desc(cookies.c.quantity)) rp = connection.execute(s) rp.fetchall() #+END_SRC #+RESULTS[223725263b0eabd2280efdb561fdde8f1e40f11d]: | oatmeal raisin | 100 | | peanut butter | 24 | | chocolate chip | 12 | | ligth chocolate chip | 10 | | dark chocolate chip | 1 | **** Limiting #+BEGIN_SRC python :results output s = select(cookies.c.cookie_name, cookies.c.quantity) s = s.order_by(cookies.c.quantity) s = s.limit(2) rp = connection.execute(s) print([result.cookie_name for result in rp]) #+END_SRC #+RESULTS[61fc508f77cde00bb8a46a1cff9f7714585d6bac]: : 2026-09-16 18:20:06,508 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_name, cookies.quantity : FROM cookies ORDER BY cookies.quantity : LIMIT ? OFFSET ? : 2026-09-16 18:20:06,512 INFO sqlalchemy.engine.Engine [generated in 0.00363s] (2, 0) : ['dark chocolate chip', 'ligth chocolate chip'] **** Functions and Labels #+BEGIN_SRC python :results output from sqlalchemy.sql import func s = select(func.sum(cookies.c.quantity)) rp = connection.execute(s) print(rp.scalar()) #+END_SRC #+RESULTS[dab408afcef7a07ab4529319e360e98963ef9253]: : 2026-09-16 18:20:06,530 INFO sqlalchemy.engine.Engine SELECT sum(cookies.quantity) AS sum_1 : FROM cookies : 2026-09-16 18:20:06,531 INFO sqlalchemy.engine.Engine [generated in 0.00018s] () : 147 #+BEGIN_SRC python :results output s = select(func.count(cookies.c.cookie_name)) rp = connection.execute(s) record = rp.first() print(record._fields) print(record.count_1) #+END_SRC #+RESULTS[cd4d9e1c7b306fcaac0f2ec879ba6eb9b8820a76]: : 2026-09-16 18:20:06,548 INFO sqlalchemy.engine.Engine SELECT count(cookies.cookie_name) AS count_1 : FROM cookies : 2026-09-16 18:20:06,548 INFO sqlalchemy.engine.Engine [generated in 0.00028s] () : ('count_1',) : 5 #+BEGIN_SRC python :results output s = select(func.count(cookies.c.cookie_name).label('inventory_count')) rp = connection.execute(s) record = rp.first() print(record._fields) print(record.inventory_count) #+END_SRC #+RESULTS[631333575ed407008ac3e744e2dd1071d2ad5fc8]: : 2026-09-16 18:20:06,572 INFO sqlalchemy.engine.Engine SELECT count(cookies.cookie_name) AS inventory_count : FROM cookies : 2026-09-16 18:20:06,572 INFO sqlalchemy.engine.Engine [generated in 0.00027s] () : ('inventory_count',) : 5 **** Filtering #+BEGIN_SRC python :results table s = select(cookies).where(cookies.c.cookie_name == "chocolate chip") rp = connection.execute(s) record = rp.first() [ (k, repr(v) if hasattr(v, "quantize") else v) for k, v in dict(record._mapping.items()).items() ] #+END_SRC #+RESULTS[eeffa461b10a57c7ba2d693a96c1074d82ddd5dd]: | cookie_id | 1 | | cookie_name | chocolate chip | | cookie_recipe_url | http://some.aweso.me/cookie/recipe.html | | cookie_sku | CC01 | | quantity | 12 | | unit_cost | Decimal('0.50') | #+BEGIN_SRC python :results output s = select(cookies).where(cookies.c.cookie_name.like("%chocolate%")) rp = connection.execute(s) for record in rp: print(record.cookie_name) #+END_SRC #+RESULTS[1923587b001fd049bf657d33ffc97e3313882d08]: : 2026-09-16 18:20:06,635 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_id, cookies.cookie_name, cookies.cookie_recipe_url, cookies.cookie_sku, cookies.quantity, cookies.unit_cost : FROM cookies : WHERE cookies.cookie_name LIKE ? : 2026-09-16 18:20:06,638 INFO sqlalchemy.engine.Engine [generated in 0.00392s] ('%chocolate%',) : chocolate chip : dark chocolate chip : ligth chocolate chip #+BEGIN_SRC python :results table s = select(cookies.c.cookie_name, "SKU-" + cookies.c.cookie_sku) [row for row in connection.execute(s)] #+END_SRC #+RESULTS[43fc9fc739efdcb3257ce1df944786476e960b19]: | chocolate chip | SKU-CC01 | | dark chocolate chip | SKU-CC02 | | ligth chocolate chip | SKU-CC03 | | peanut butter | SKU-PB01 | | oatmeal raisin | SKU-EWW01 | #+BEGIN_SRC python :results table from sqlalchemy import cast s = select( cookies.c.cookie_name, cast((cookies.c.quantity * cookies.c.unit_cost), Numeric(12, 2)).label("inv_cost"), ) [(row.cookie_name, repr(row.inv_cost)) for row in connection.execute(s)] #+END_SRC #+RESULTS[e77d9a46c98a0d29f5d2e887b32d948c8aff4b24]: | chocolate chip | Decimal('6.00') | | dark chocolate chip | Decimal('0.75') | | ligth chocolate chip | Decimal('5.00') | | peanut butter | Decimal('6.00') | | oatmeal raisin | Decimal('100.00') | **** Conjunctions #+BEGIN_SRC python :results table from sqlalchemy import and_, or_, not_ s = select(cookies).where(and_(cookies.c.quantity > 23, cookies.c.unit_cost < 0.40)) [row.cookie_name for row in connection.execute(s)] #+END_SRC #+RESULTS[26e274d9d4a427015efa928c324c4c0424561b44]: | peanut butter | #+BEGIN_SRC python :results table s = select(cookies).where( or_(cookies.c.quantity.between(10, 50), cookies.c.cookie_name.contains("chip")) ) [(row.cookie_name,) for row in connection.execute(s)] #+END_SRC #+RESULTS[59dc52c5605d049ac5a987e4586dc5435b449116]: | chocolate chip | | dark chocolate chip | | ligth chocolate chip | | peanut butter | *** Updating Data #+BEGIN_SRC python :results value from sqlalchemy import update u = update(cookies).where(cookies.c.cookie_name == "chocolate chip") u = u.values(quantity=(cookies.c.quantity + 120)) result = connection.execute(u) result.rowcount #+END_SRC #+RESULTS[847d575b29c958aac6097fd1f5f38ab15233c881]: : 1 #+BEGIN_SRC python :results table s = select(cookies).where(cookies.c.cookie_name == "chocolate chip") result = connection.execute(s).first() [ ( key, ( repr(result._mapping[key]) if hasattr(result._mapping[key], "quantize") else result._mapping[key] ), ) for key in result._mapping.keys() ] #+END_SRC #+RESULTS[3b87446799c9d1eafeac3f98a9b95850e0392495]: | cookie_id | 1 | | cookie_name | chocolate chip | | cookie_recipe_url | http://some.aweso.me/cookie/recipe.html | | cookie_sku | CC01 | | quantity | 132 | | unit_cost | Decimal('0.50') | *** Deleting Data #+BEGIN_SRC python :results value from sqlalchemy import delete u = delete(cookies).where(cookies.c.cookie_name == "dark chocolate chip") result = connection.execute(u) result.rowcount #+END_SRC #+RESULTS[0ca3bff6e5691d71853f6a179a07305f7927b9c5]: : 1 #+BEGIN_SRC python :results value s = select(cookies).where(cookies.c.cookie_name == "dark chocolate chip") result = connection.execute(s).fetchall() len(result) #+END_SRC #+RESULTS[0186e481f8730015502615c0c5317b7021defdab]: : 0 *** Joins #+BEGIN_SRC python :results silent 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 = users.insert() result = connection.execute(ins, customer_list) #+END_SRC #+BEGIN_SRC python :results none ins = insert(orders).values(user_id=1, order_id=1) result = connection.execute(ins) ins = insert(line_items) order_items = [ {"order_id": 1, "cookie_id": 1, "quantity": 2, "extended_cost": 1.00}, {"order_id": 1, "cookie_id": 3, "quantity": 12, "extended_cost": 3.00}, ] result = connection.execute(ins, order_items) ins = insert(orders).values(user_id=2, order_id=2) result = connection.execute(ins) ins = insert(line_items) order_items = [ {"order_id": 2, "cookie_id": 1, "quantity": 24, "extended_cost": 12.00}, {"order_id": 2, "cookie_id": 4, "quantity": 6, "extended_cost": 6.00}, ] result = connection.execute(ins, order_items) #+END_SRC #+BEGIN_SRC python :results table s = select(line_items) rp = connection.execute(s) [tuple(f for f in rp.keys())] + [row for row in rp] #+END_SRC #+RESULTS[6c67ac82f1b3fa17449e94614678bdd9b9a86c01]: | line_items_id | order_id | cookie_id | quantity | extended_cost | | | 1 | 1 | 1 | 2 | Decimal | (1.00) | | 2 | 1 | 3 | 12 | Decimal | (3.00) | | 3 | 2 | 1 | 24 | Decimal | (12.00) | | 4 | 2 | 4 | 6 | Decimal | (6.00) | #+BEGIN_SRC python :results table s = select(orders) rp = connection.execute(s) [tuple(f for f in rp.keys())] + [row for row in rp] #+END_SRC #+RESULTS[d267590cf86f6d096b1bb878728114e7c6714fb0]: | order_id | user_id | shipped | | 1 | 1 | False | | 2 | 2 | False | #+BEGIN_SRC python :results table s = select(users) rp = connection.execute(s) [tuple(f for f in rp.keys())] + [row for row in rp] #+END_SRC #+RESULTS[5ecd8eac1228fddbc2304d381cc0148fc740d30d]: | user_id | username | email_address | phone | password | created_on | updated_on | | | | 1 | cookiemon | mon@cookie.com | 111-111-1111 | password | datetime.datetime | (2026 9 16 18 20 6 947339) | datetime.datetime | (2026 9 16 18 20 6 947349) | | 2 | cakeeater | cakeeater@cake.com | 222-222-2222 | password | datetime.datetime | (2026 9 16 18 20 6 947352) | datetime.datetime | (2026 9 16 18 20 6 947355) | | 3 | pieguy | guy@pie.com | 333-333-3333 | password | datetime.datetime | (2026 9 16 18 20 6 947359) | datetime.datetime | (2026 9 16 18 20 6 947360) | #+BEGIN_SRC python :results table :cache no columns = [ orders.c.order_id, users.c.username, users.c.phone, cookies.c.cookie_name, line_items.c.quantity, line_items.c.extended_cost, ] cookiemon_orders = ( select(*columns) .select_from( orders .join(users, orders.c.user_id == users.c.user_id) .join(line_items, orders.c.order_id == line_items.c.order_id) .join(cookies, line_items.c.cookie_id == cookies.c.cookie_id) ) .where(users.c.username == "cookiemon") ) result = connection.execute(cookiemon_orders) [row for row in result] #+END_SRC #+RESULTS: | 1 | cookiemon | 111-111-1111 | chocolate chip | 2 | Decimal | (1.00) | | 1 | cookiemon | 111-111-1111 | ligth chocolate chip | 12 | Decimal | (3.00) | ** SQLAlchemy v2 :PROPERTIES: :header-args:python: :cache no :header-args:python: :tangle ch2-v2.py :header-args: :session *ch2v2* :END: #+BEGIN_SRC python :results none :shebang "#!/usr/bin/env python3" from datetime import datetime from sqlalchemy import ( Boolean, CheckConstraint, Column, DateTime, ForeignKey, Integer, MetaData, Numeric, PrimaryKeyConstraint, String, Table, UniqueConstraint, create_engine, ) from sqlparse import format as sqlformat def sqlf( sql: str | object, reindent=True, keyword_case="upper", indent_width=2, strip_comments=False, identifier_case="lower", ): if not isinstance(sql, str): sql = str(sql) return sqlformat( sql, encoding="utf8", reindent=reindent, keyword_case=keyword_case, indent_width=indent_width, strip_comments=strip_comments, identifier_case=identifier_case, ) 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), unique=True), Column("cookie_sku", String(55), unique=True), Column("quantity", Integer()), Column("unit_cost", Numeric(12, 2)), CheckConstraint("unit_cost >= 0.00", name="unit_cost_positive"), ) users = Table( "users", metadata, Column("user_id", Integer()), Column("username", String(15), nullable=False), 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), PrimaryKeyConstraint("user_id", name="user_pk"), UniqueConstraint("username", name="uix_username"), ) orders = Table( "orders", metadata, Column("order_id", Integer(), primary_key=True), 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)), ) from sqlalchemy.pool import StaticPool engine = create_engine("sqlite:///:memory:", echo=True) connection = engine.connect() metadata.create_all(connection) #+END_SRC *** Insert Data #+BEGIN_SRC python :results value ins = cookies.insert().values( cookie_name="chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe.html", cookie_sku="CC01", quantity="12", unit_cost="0.50" ) sqlf(ins) #+END_SRC #+RESULTS[b62598d5e632b067ebfae4b1023821ee0e7091b6]: : INSERT INTO cookies (cookie_name, cookie_recipe_url, cookie_sku, quantity, unit_cost) : VALUES (:cookie_name, :cookie_recipe_url, :cookie_sku, :quantity, :unit_cost) #+BEGIN_SRC python :results value import json json.dumps(ins.compile().params, indent=2) #+END_SRC #+RESULTS[0018258860595918dc2f188cc9c30a07ddb14922]: : { : "cookie_name": "chocolate chip", : "cookie_recipe_url": "http://some.aweso.me/cookie/recipe.html", : "cookie_sku": "CC01", : "quantity": "12", : "unit_cost": "0.50" : } #+BEGIN_SRC python :results table result = connection.execute(ins) connection.commit() result.inserted_primary_key #+END_SRC #+RESULTS[79ef993cf722a486accb9755751bc78723fc38e2]: | 1 | #+BEGIN_SRC python :results table ins = cookies.insert() result = 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, ), ) connection.commit() result.inserted_primary_key #+END_SRC #+RESULTS[0e9166654b4c44b69add0fb4c1c060b11ed4f926]: | 2 | #+BEGIN_SRC python :results table from sqlalchemy import insert ins = insert(cookies).values( cookie_name="ligth chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe_light.html", cookie_sku="CC03", quantity=10, unit_cost=0.5 ) result = connection.execute(ins) connection.commit() result.inserted_primary_key #+END_SRC #+RESULTS[676c23b01f138d7d6fb622cc3cbf3b77eed25d1a]: | 3 | #+BEGIN_SRC python :results table ins = cookies.insert() inventory_list = [ { "cookie_name": "peanut butter", "cookie_recipe_url": "http://some.aweso.me/cookie/panut.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 } ] result = connection.execute(ins, inventory_list) connection.commit() result.rowcount #+END_SRC #+RESULTS[c3ed83418f832724ad79a751480a4f4cd6c6c498]: | 2 | *** Querying Data #+BEGIN_SRC python :results table from sqlalchemy import select s = select(cookies) result = connection.execute(s) result.all() #+END_SRC #+RESULTS[3640959102aca0eaee952f069da52aacf1e93bbe]: | 1 | chocolate chip | http://some.aweso.me/cookie/recipe.html | CC01 | 12 | Decimal | (0.50) | | 2 | dark chocolate chip | http://some.aweso.me/cookie/recipe_dark.html | CC02 | 1 | Decimal | (0.75) | | 3 | ligth chocolate chip | http://some.aweso.me/cookie/recipe_light.html | CC03 | 10 | Decimal | (0.50) | | 4 | peanut butter | http://some.aweso.me/cookie/panut.html | PB01 | 24 | Decimal | (0.25) | | 5 | oatmeal raisin | http://some.okay.me/cookie/raisin.html | EWW01 | 100 | Decimal | (1.00) | #+BEGIN_SRC python :results table s = select(cookies) results = connection.execute(s).all() results #+END_SRC #+RESULTS[fe970d04609a1b31e98475c19bda532182fd1b60]: | 1 | chocolate chip | http://some.aweso.me/cookie/recipe.html | CC01 | 12 | Decimal | (0.50) | | 2 | dark chocolate chip | http://some.aweso.me/cookie/recipe_dark.html | CC02 | 1 | Decimal | (0.75) | | 3 | ligth chocolate chip | http://some.aweso.me/cookie/recipe_light.html | CC03 | 10 | Decimal | (0.50) | | 4 | peanut butter | http://some.aweso.me/cookie/panut.html | PB01 | 24 | Decimal | (0.25) | | 5 | oatmeal raisin | http://some.okay.me/cookie/raisin.html | EWW01 | 100 | Decimal | (1.00) | **** Result #+BEGIN_SRC python :results table first_row = results[0] ( first_row[1], first_row.cookie_name, first_row._mapping[cookies.c.cookie_name] ) #+END_SRC #+RESULTS[2ead18cf1a413cd3284e77709422a06a3900433b]: | chocolate chip | chocolate chip | chocolate chip | #+BEGIN_SRC python :results output rp = connection.execute(select(cookies)) for record in rp: print(record.cookie_name) #+END_SRC #+RESULTS[c40dd53b6f4d7186421b259a0e1b11342abd78c2]: : 2026-09-16 19:25:14,506 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_id, cookies.cookie_name, cookies.cookie_recipe_url, cookies.cookie_sku, cookies.quantity, cookies.unit_cost : FROM cookies : 2026-09-16 19:25:14,506 INFO sqlalchemy.engine.Engine [cached since 0.09276s ago] () : chocolate chip : dark chocolate chip : ligth chocolate chip : peanut butter : oatmeal raisin **** Controlling the Columns in the Query #+BEGIN_SRC python :results output s = select(cookies.c.cookie_name, cookies.c.quantity) rp = connection.execute(s) print(rp.keys()) result = rp.first() print(result) #+END_SRC #+RESULTS[9bd5d9f918c47340cf4915a5206b1e6ea9409427]: : 2026-09-16 19:25:14,520 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_name, cookies.quantity : FROM cookies : 2026-09-16 19:25:14,521 INFO sqlalchemy.engine.Engine [generated in 0.00015s] () : RMKeyView(['cookie_name', 'quantity']) : ('chocolate chip', 12) **** Ordering #+BEGIN_SRC python :results output s = select(cookies.c.cookie_name, cookies.c.quantity) s = s.order_by(cookies.c.quantity) rp = connection.execute(s) for cookie in rp: print("{} - {}".format(cookie.quantity, cookie.cookie_name)) #+END_SRC #+RESULTS[81dbba92b0ffa27f5dec5f713d59ab32f1ad2f78]: : 2026-09-16 19:25:14,532 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_name, cookies.quantity : FROM cookies ORDER BY cookies.quantity : 2026-09-16 19:25:14,532 INFO sqlalchemy.engine.Engine [generated in 0.00017s] () : 1 - dark chocolate chip : 10 - ligth chocolate chip : 12 - chocolate chip : 24 - peanut butter : 100 - oatmeal raisin #+BEGIN_SRC python :results table from sqlalchemy import desc s = select(cookies.c.cookie_name, cookies.c.quantity) s = s.order_by(desc(cookies.c.quantity)) rp = connection.execute(s) rp.all() #+END_SRC #+RESULTS[bafc4ff430e023ef53ecaedafba7ca47bd89a027]: | oatmeal raisin | 100 | | peanut butter | 24 | | chocolate chip | 12 | | ligth chocolate chip | 10 | | dark chocolate chip | 1 | **** Limiting #+BEGIN_SRC python :results output s = select(cookies.c.cookie_name, cookies.c.quantity) s = s.order_by(cookies.c.quantity) s = s.limit(2) rp = connection.execute(s) print([result.cookie_name for result in rp]) #+END_SRC #+RESULTS[42c818a0ef9918c538fc39f00bc2012b619081a9]: : 2026-09-16 19:25:14,582 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_name, cookies.quantity : FROM cookies ORDER BY cookies.quantity : LIMIT ? OFFSET ? : 2026-09-16 19:25:14,582 INFO sqlalchemy.engine.Engine [generated in 0.00020s] (2, 0) : ['dark chocolate chip', 'ligth chocolate chip'] **** Functions and Labels #+BEGIN_SRC python :results output from sqlalchemy import func s = select(func.sum(cookies.c.quantity)) rp = connection.execute(s) print(rp.scalar()) #+END_SRC #+RESULTS[8aa53360a1f7553d0919165cc2e0395cd9cdbf75]: : 2026-09-16 19:25:14,596 INFO sqlalchemy.engine.Engine SELECT sum(cookies.quantity) AS sum_1 : FROM cookies : 2026-09-16 19:25:14,596 INFO sqlalchemy.engine.Engine [generated in 0.00015s] () : 147 #+BEGIN_SRC python :results output s = select(func.count(cookies.c.cookie_name)) rp = connection.execute(s) record = rp.first() print(record._fields) print(record.count_1) #+END_SRC #+RESULTS[7da8cb0840185a44ad15f744854e7ddcc43142e2]: : 2026-09-16 19:25:14,609 INFO sqlalchemy.engine.Engine SELECT count(cookies.cookie_name) AS count_1 : FROM cookies : 2026-09-16 19:25:14,610 INFO sqlalchemy.engine.Engine [generated in 0.00014s] () : ('count_1',) : 5 #+BEGIN_SRC python :results output s = select(func.count(cookies.c.cookie_name).label('inventory_count')) rp = connection.execute(s) record = rp.first() print(record._fields) print(record.inventory_count) #+END_SRC #+RESULTS[e3ae661197627aa382aebcc7886d9dbfcc9f7648]: : 2026-09-16 19:25:14,623 INFO sqlalchemy.engine.Engine SELECT count(cookies.cookie_name) AS inventory_count : FROM cookies : 2026-09-16 19:25:14,623 INFO sqlalchemy.engine.Engine [generated in 0.00015s] () : ('inventory_count',) : 5 **** Filtering #+BEGIN_SRC python :results table s = select(cookies).where(cookies.c.cookie_name == "chocolate chip") rp = connection.execute(s) record = rp.first() [ (k, repr(v) if hasattr(v, "quantize") else v) for k, v in dict(record._mapping.items()).items() ] #+END_SRC #+RESULTS[f9be443253c771bf63bf0e220aba3afb69fdacd8]: | cookie_id | 1 | | cookie_name | chocolate chip | | cookie_recipe_url | http://some.aweso.me/cookie/recipe.html | | cookie_sku | CC01 | | quantity | 12 | | unit_cost | Decimal('0.50') | #+BEGIN_SRC python :results output s = select(cookies).where(cookies.c.cookie_name.like("%chocolate%")) rp = connection.execute(s) for record in rp: print(record.cookie_name) #+END_SRC #+RESULTS[7ddf89caaefaac2bc4e5dfc1c808da3262d8a6dd]: : 2026-09-16 19:25:14,672 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_id, cookies.cookie_name, cookies.cookie_recipe_url, cookies.cookie_sku, cookies.quantity, cookies.unit_cost : FROM cookies : WHERE cookies.cookie_name LIKE ? : 2026-09-16 19:25:14,672 INFO sqlalchemy.engine.Engine [generated in 0.00017s] ('%chocolate%',) : chocolate chip : dark chocolate chip : ligth chocolate chip #+BEGIN_SRC python :results table s = select(cookies.c.cookie_name, "SKU-" + cookies.c.cookie_sku) [row for row in connection.execute(s)] #+END_SRC #+RESULTS[5f464e9ee3e60c403cf2477cad6a17b20be05ad9]: | chocolate chip | SKU-CC01 | | dark chocolate chip | SKU-CC02 | | ligth chocolate chip | SKU-CC03 | | peanut butter | SKU-PB01 | | oatmeal raisin | SKU-EWW01 | #+BEGIN_SRC python :results table from sqlalchemy import cast s = select( cookies.c.cookie_name, cast((cookies.c.quantity * cookies.c.unit_cost), Numeric(12, 2)).label("inv_cost"), ) [(row.cookie_name, repr(row.inv_cost)) for row in connection.execute(s)] #+END_SRC #+RESULTS[ca2b3c83ecfc01a7899510f79bd4631436fbb199]: | chocolate chip | Decimal('6.00') | | dark chocolate chip | Decimal('0.75') | | ligth chocolate chip | Decimal('5.00') | | peanut butter | Decimal('6.00') | | oatmeal raisin | Decimal('100.00') | **** Conjunctions #+BEGIN_SRC python :results table from sqlalchemy import and_, or_ s = select(cookies).where(and_(cookies.c.quantity > 23, cookies.c.unit_cost < 0.40)) [row.cookie_name for row in connection.execute(s)] #+END_SRC #+RESULTS[696e8f93933efd5bc78e28b8e6de3edb94aa8e71]: | peanut butter | #+BEGIN_SRC python :results table s = select(cookies).where( or_(cookies.c.quantity.between(10, 50), cookies.c.cookie_name.contains("chip")) ) [(row.cookie_name,) for row in connection.execute(s)] #+END_SRC #+RESULTS[3db4a872df1b72d8f67560300e527b4d065be69e]: | chocolate chip | | dark chocolate chip | | ligth chocolate chip | | peanut butter | *** Updating Data #+BEGIN_SRC python :results value from sqlalchemy import update u = update(cookies).where(cookies.c.cookie_name == "chocolate chip") u = u.values(quantity=(cookies.c.quantity + 120)) result = connection.execute(u) connection.commit() result.rowcount #+END_SRC #+RESULTS[2d41dc1439f6c66b5fab0b2bcb50ef86a546facd]: : 1 #+BEGIN_SRC python :results table s = select(cookies).where(cookies.c.cookie_name == "chocolate chip") result = connection.execute(s).first() [ ( key, ( repr(result._mapping[key]) if hasattr(result._mapping[key], "quantize") else result._mapping[key] ), ) for key in result._mapping.keys() ] #+END_SRC #+RESULTS[df3fbfc80a790c9cada7e56c8eb1cc9c44608632]: | cookie_id | 1 | | cookie_name | chocolate chip | | cookie_recipe_url | http://some.aweso.me/cookie/recipe.html | | cookie_sku | CC01 | | quantity | 132 | | unit_cost | Decimal('0.50') | *** Deleting Data #+BEGIN_SRC python :results value from sqlalchemy import delete u = delete(cookies).where(cookies.c.cookie_name == "dark chocolate chip") result = connection.execute(u) connection.commit() result.rowcount #+END_SRC #+RESULTS[c78a60f97e57cedbde3dec71a2c7c9594cb0b821]: : 1 #+BEGIN_SRC python :results value s = select(cookies).where(cookies.c.cookie_name == "dark chocolate chip") result = connection.execute(s).all() len(result) #+END_SRC #+RESULTS[48f1fb3f9cd64941c3b6263bb9f6f6fb9b608a12]: : 0 *** Joins #+BEGIN_SRC python :results silent 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 = users.insert() result = connection.execute(ins, customer_list) connection.commit() #+END_SRC #+BEGIN_SRC python :results none ins = insert(orders).values(user_id=1, order_id=1) result = connection.execute(ins) ins = insert(line_items) order_items = [ {"order_id": 1, "cookie_id": 1, "quantity": 2, "extended_cost": 1.00}, {"order_id": 1, "cookie_id": 3, "quantity": 12, "extended_cost": 3.00}, ] result = connection.execute(ins, order_items) ins = insert(orders).values(user_id=2, order_id=2) result = connection.execute(ins) ins = insert(line_items) order_items = [ {"order_id": 2, "cookie_id": 1, "quantity": 24, "extended_cost": 12.00}, {"order_id": 2, "cookie_id": 4, "quantity": 6, "extended_cost": 6.00}, ] result = connection.execute(ins, order_items) connection.commit() #+END_SRC #+BEGIN_SRC python :results table s = select(line_items) rp = connection.execute(s) [tuple(f for f in rp.keys())] + [row for row in rp] #+END_SRC #+RESULTS[1fcc0e794bcbe7a7f531be1ad71cc89cdb0a3099]: | line_items_id | order_id | cookie_id | quantity | extended_cost | | | 1 | 1 | 1 | 2 | Decimal | (1.00) | | 2 | 1 | 3 | 12 | Decimal | (3.00) | | 3 | 2 | 1 | 24 | Decimal | (12.00) | | 4 | 2 | 4 | 6 | Decimal | (6.00) | #+BEGIN_SRC python :results table s = select(orders) rp = connection.execute(s) [tuple(f for f in rp.keys())] + [row for row in rp] #+END_SRC #+RESULTS[ab29e60937fe14ffa13a62e686f2b1d2c38b2685]: | order_id | user_id | shipped | | 1 | 1 | False | | 2 | 2 | False | #+BEGIN_SRC python :results table s = select(users) rp = connection.execute(s) [tuple(f for f in rp.keys())] + [row for row in rp] #+END_SRC #+RESULTS[ddc5b43682386ff382d6c09fd6af055077a29600]: | user_id | username | email_address | phone | password | created_on | updated_on | | | | 1 | cookiemon | mon@cookie.com | 111-111-1111 | password | datetime.datetime | (2026 9 16 19 25 14 928945) | datetime.datetime | (2026 9 16 19 25 14 928953) | | 2 | cakeeater | cakeeater@cake.com | 222-222-2222 | password | datetime.datetime | (2026 9 16 19 25 14 928955) | datetime.datetime | (2026 9 16 19 25 14 928956) | | 3 | pieguy | guy@pie.com | 333-333-3333 | password | datetime.datetime | (2026 9 16 19 25 14 928957) | datetime.datetime | (2026 9 16 19 25 14 928958) | #+BEGIN_SRC python :results table :cache no columns = [ orders.c.order_id, users.c.username, users.c.phone, cookies.c.cookie_name, line_items.c.quantity, line_items.c.extended_cost, ] cookiemon_orders = ( select(*columns) .select_from( orders .join(users, orders.c.user_id == users.c.user_id) .join(line_items, orders.c.order_id == line_items.c.order_id) .join(cookies, line_items.c.cookie_id == cookies.c.cookie_id) ) .where(users.c.username == "cookiemon") ) result = connection.execute(cookiemon_orders) [row for row in result] #+END_SRC #+RESULTS: | 1 | cookiemon | 111-111-1111 | chocolate chip | 2 | Decimal | (1.00) | | 1 | cookiemon | 111-111-1111 | ligth chocolate chip | 12 | Decimal | (3.00) | * Chapter 3: Exceptions and Transactions ** SQLAlchemy v1 :PROPERTIES: :header-args: :cache no :header-args:python: :tangle ch3-v1.py :header-args: :session *ch3* :END: #+BEGIN_SRC python :shebang "#!/usr/bin/env python3" :results none import sys sys.stderr = sys.stdout from datetime import datetime from sqlalchemy import ( Boolean, CheckConstraint, Column, DateTime, ForeignKey, Integer, MetaData, Numeric, String, Table, create_engine, PrimaryKeyConstraint, UniqueConstraint, ) from sqlparse import format as sqlformat def sqlf( sql: str | object, reindent=True, keyword_case="upper", indent_width=2, strip_comments=False, identifier_case="lower", ): if not isinstance(sql, str): sql = str(sql) return sqlformat( sql, encoding="utf8", reindent=reindent, keyword_case=keyword_case, indent_width=indent_width, strip_comments=strip_comments, identifier_case=identifier_case, ) 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), unique=True), Column("cookie_sku", String(55), unique=True), Column("quantity", Integer()), Column("unit_cost", Numeric(12, 2)), CheckConstraint("unit_cost >= 0.00", name="unit_cost_positive"), CheckConstraint("quantity >= 0", name="quantity_positive"), ) users = Table( "users", metadata, Column("user_id", Integer()), Column("username", String(15), nullable=False), 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), PrimaryKeyConstraint("user_id", name="user_pk"), UniqueConstraint("username", name="uix_username"), ) orders = Table( "orders", metadata, Column("order_id", Integer(), primary_key=True), 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)), ) engine = create_engine("sqlite:///:memory:", echo=True) connection = engine.connect() metadata.create_all(engine) #+END_SRC *** AttributeError #+BEGIN_SRC python :results output import logging from sqlalchemy import select, insert ins = insert(users).values( username="cookiemon", email_address="mon@cookie.com", phone="111-111-1111", password="password", ) result = connection.execute(ins) s = select(users.c.username) results = connection.execute(s) try: [(result.username, result.password) for result in results] except AttributeError as exc: logging.exception(results) #+END_SRC #+RESULTS: #+begin_example 2026-09-18 12:06:22,953 INFO sqlalchemy.engine.Engine BEGIN (implicit) INFO:sqlalchemy.engine.Engine:BEGIN (implicit) 2026-09-18 12:06:22,954 INFO sqlalchemy.engine.Engine INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (?, ?, ?, ?, ?, ?) INFO:sqlalchemy.engine.Engine:INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (?, ?, ?, ?, ?, ?) 2026-09-18 12:06:22,954 INFO sqlalchemy.engine.Engine [generated in 0.00036s] ('cookiemon', 'mon@cookie.com', '111-111-1111', 'password', '2026-09-18 12:06:22.953895', '2026-09-18 12:06:22.953904') INFO:sqlalchemy.engine.Engine:[generated in 0.00036s] ('cookiemon', 'mon@cookie.com', '111-111-1111', 'password', '2026-09-18 12:06:22.953895', '2026-09-18 12:06:22.953904') 2026-09-18 12:06:22,954 INFO sqlalchemy.engine.Engine SELECT users.username FROM users INFO:sqlalchemy.engine.Engine:SELECT users.username FROM users 2026-09-18 12:06:22,954 INFO sqlalchemy.engine.Engine [generated in 0.00014s] () INFO:sqlalchemy.engine.Engine:[generated in 0.00014s] () ERROR:root: Traceback (most recent call last): File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/result.py", line 211, in _key_not_found self._key_fallback(key, None) ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/cursor.py", line 851, in _key_fallback raise exc.NoSuchColumnError( ...<2 lines>... ) from err sqlalchemy.exc.NoSuchColumnError: Could not locate column in row for column 'password' The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/tmp/babel-sg4Ps9/python-VQah8h", line 17, in [(result.username, result.password) for result in results] ^^^^^^^^^^^^^^^ File "lib/sqlalchemy/cyextension/resultproxy.pyx", line 66, in sqlalchemy.cyextension.resultproxy.BaseRow.__getattr__ File "lib/sqlalchemy/cyextension/resultproxy.pyx", line 63, in sqlalchemy.cyextension.resultproxy.BaseRow._get_by_key_impl File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/result.py", line 213, in _key_not_found raise AttributeError(ke.args[0]) from ke AttributeError: Could not locate column in row for column 'password' #+end_example *** IntegrityError #+BEGIN_SRC python :results output from sqlalchemy.exc import IntegrityError s = select(users.c.username) print(connection.execute(s).fetchall()) ins = insert(users).values( username="cookiemon", email_address="damon@cookie.com", phone="111-111-1111", password="password", ) try: result = connection.execute(ins) except IntegrityError as exc: logging.exception(str(ins)) #+END_SRC #+RESULTS[cdbcb9c1f18b5c62c2127f1ac9ffd14b2a38039e]: #+begin_example 2026-09-18 12:06:22,969 INFO sqlalchemy.engine.Engine SELECT users.username FROM users INFO:sqlalchemy.engine.Engine:SELECT users.username FROM users 2026-09-18 12:06:22,969 INFO sqlalchemy.engine.Engine [cached since 0.01439s ago] () INFO:sqlalchemy.engine.Engine:[cached since 0.01439s ago] () [('cookiemon',)] 2026-09-18 12:06:22,969 INFO sqlalchemy.engine.Engine INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (?, ?, ?, ?, ?, ?) INFO:sqlalchemy.engine.Engine:INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (?, ?, ?, ?, ?, ?) 2026-09-18 12:06:22,969 INFO sqlalchemy.engine.Engine [cached since 0.0159s ago] ('cookiemon', 'damon@cookie.com', '111-111-1111', 'password', '2026-09-18 12:06:22.969623', '2026-09-18 12:06:22.969627') INFO:sqlalchemy.engine.Engine:[cached since 0.0159s ago] ('cookiemon', 'damon@cookie.com', '111-111-1111', 'password', '2026-09-18 12:06:22.969623', '2026-09-18 12:06:22.969627') 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-Dqwfvm", line 13, in result = 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:06:22.969623', '2026-09-18 12:06:22.969627')] (Background on this error at: https://sqlalche.me/e/20/gkpj) #+end_example *** Transactions #+BEGIN_SRC python :results none ins = cookies.insert() inventory_list = [ { "cookie_name": "chocolate chip", "cookie_recipe_url": "http://some.aweso.me/cookie/recipe.html", "cookie_sku": "CC01", "quantity": "12", "unit_cost": "0.5", }, { "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", }, ] result = connection.execute(ins, inventory_list) #+END_SRC #+BEGIN_SRC python :results none ins = insert(orders).values(user_id=1, order_id="1") result = connection.execute(ins) ins = insert(line_items) order_items = [ { "order_id": 1, "cookie_id": 1, "quantity": 9, "extended_cost": 4.50, } ] result = connection.execute(ins, order_items) ins = insert(orders).values(user_id=1, order_id="2") result = connection.execute(ins) ins = insert(line_items) order_items = [ { "order_id": 2, "cookie_id": 1, "quantity": 4, "extended_cost": 1.50, }, { "order_id": 2, "cookie_id": 2, "quantity": 1, "extended_cost": 4.50, }, ] result = connection.execute(ins, order_items) #+END_SRC #+BEGIN_SRC python :results none from sqlalchemy import update def ship_it(order_id: int) -> None: s = select(line_items.c.cookie_id, line_items.c.quantity) s = s.where(line_items.c.order_id == order_id) cookies_to_ship = connection.execute(s) for cookie in cookies_to_ship: u = ( update(cookies) .where(cookies.c.cookie_id == cookie.cookie_id) .values(quantity=cookies.c.quantity - cookie.quantity) ) result = connection.execute(u) u = update(orders).where(orders.c.order_id == order_id).values(shipped=True) connection.execute(u) print(f"Shipped order ID: {order_id}") #+END_SRC #+BEGIN_SRC python :results output ship_it(1) s = select(cookies.c.cookie_name, cookies.c.quantity) print(connection.execute(s).fetchall()) #+END_SRC #+RESULTS: #+begin_example 2026-09-18 12:06:23,066 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:06:23,066 INFO sqlalchemy.engine.Engine [generated in 0.00024s] (1,) INFO:sqlalchemy.engine.Engine:[generated in 0.00024s] (1,) 2026-09-18 12:06:23,067 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:06:23,067 INFO sqlalchemy.engine.Engine [generated in 0.00009s] (9, 1) INFO:sqlalchemy.engine.Engine:[generated in 0.00009s] (9, 1) 2026-09-18 12:06:23,067 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:06:23,067 INFO sqlalchemy.engine.Engine [generated in 0.00008s] (1, 1) INFO:sqlalchemy.engine.Engine:[generated in 0.00008s] (1, 1) Shipped order ID: 1 2026-09-18 12:06:23,067 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:06:23,067 INFO sqlalchemy.engine.Engine [generated in 0.00006s] () INFO:sqlalchemy.engine.Engine:[generated in 0.00006s] () [('chocolate chip', 3), ('dark chocolate chip', 1)] #+end_example #+BEGIN_SRC python :results output try: ship_it(2) except IntegrityError: logging.exception("") #+END_SRC #+RESULTS: #+begin_example 2026-09-18 12:07:29,522 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:07:29,522 INFO sqlalchemy.engine.Engine [cached since 66.46s ago] (2,) INFO:sqlalchemy.engine.Engine:[cached since 66.46s ago] (2,) 2026-09-18 12:07:29,523 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:07:29,523 INFO sqlalchemy.engine.Engine [cached since 66.46s ago] (4, 1) INFO:sqlalchemy.engine.Engine:[cached since 66.46s ago] (4, 1) 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-swDwMp", line 2, in ship_it(2) ~~~~~~~^^^ File "", line 14, in ship_it 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 #+BEGIN_SRC python :results none :cache yes def ship_it(order_id: int) -> None: s = select(line_items.c.cookie_id, line_items.c.quantity) s = s.where(line_items.c.order_id == order_id) transaction = connection.begin() cookies_to_ship = connection.execute(s).fetchall() try: for cookie in cookies_to_ship: u = update(cookies).where(cookies.c.cookie_id == cookie.cookie_id).values(quantity = cookies.c.quantity -cookie.quantity) result = connection.execute(u) u = update(orders).where(orders.c.order_id == order_id).values(shipped=True) result = connection.execute(u) print(f"Shipped order ID: {order_id}") transaction.commit() except IntegrityError as error: transaction.rollback() logging.exception() #+END_SRC ** SQLAlchemy v2 :PROPERTIES: :header-args: :cache yes :header-args:python: :tangle ch3-v2.py :header-args: :session *ch3-v2* :END: #+BEGIN_SRC python :results output :shebang "#!/usr/bin/env python3" #!/usr/bin/env python3 import sys sys.stderr = sys.stdout import logging from datetime import datetime from sqlalchemy import ( Boolean, CheckConstraint, Column, DateTime, ForeignKey, Integer, MetaData, Numeric, PrimaryKeyConstraint, String, Table, UniqueConstraint, create_engine, insert, select, update, ) from sqlalchemy.exc import IntegrityError from sqlparse import format as sqlformat def sqlf( sql: str | object, reindent=True, keyword_case="upper", indent_width=2, strip_comments=False, identifier_case="lower", ): if not isinstance(sql, str): sql = str(sql) return sqlformat( sql, encoding="utf8", reindent=reindent, keyword_case=keyword_case, indent_width=indent_width, strip_comments=strip_comments, identifier_case=identifier_case, ) 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), unique=True), Column("cookie_sku", String(55), unique=True), Column("quantity", Integer()), Column("unit_cost", Numeric(12, 2)), CheckConstraint("unit_cost >= 0.00", name="unit_cost_positive"), CheckConstraint("quantity >= 0", name="quantity_positive"), ) users = Table( "users", metadata, Column("user_id", Integer()), Column("username", String(15), nullable=False), 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), PrimaryKeyConstraint("user_id", name="user_pk"), UniqueConstraint("username", name="uix_username"), ) orders = Table( "orders", metadata, Column("order_id", Integer(), primary_key=True), 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)), ) engine = create_engine("sqlite:///:memory:", echo=True) with engine.begin() as connection: metadata.create_all(connection) # --- insert одного пользователя --- ins = insert(users).values( username="cookiemon", email_address="mon@cookie.com", phone="111-111-1111", password="password", ) connection.execute(ins) # --- select возвращает Row, у которого есть .username --- s = select(users.c.username, users.c.password) results = connection.execute(s) try: [(row.username, row.password) for row in results] except AttributeError: logging.exception(results) # --- проверка вставки --- s = select(users.c.username) print(connection.execute(s).fetchall()) # --- нарушение уникальности username --- ins = insert(users).values( username="cookiemon", email_address="damon@cookie.com", phone="111-111-1111", password="password", ) try: with connection.begin_nested(): # SAVEPOINT, чтобы не убить внешнюю транзакцию connection.execute(ins) except IntegrityError: logging.exception(str(ins)) # --- массовая вставка cookies --- inventory_list = [ { "cookie_name": "chocolate chip", "cookie_recipe_url": "http://some.aweso.me/cookie/recipe.html", "cookie_sku": "CC01", "quantity": "12", "unit_cost": "0.5", }, { "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", }, ] connection.execute(insert(cookies), inventory_list) # --- заказы --- connection.execute(insert(orders).values(user_id=1, order_id="1")) connection.execute( insert(line_items), [ { "order_id": 1, "cookie_id": 1, "quantity": 9, "extended_cost": 4.50, } ], ) connection.execute(insert(orders).values(user_id=1, order_id="2")) connection.execute( insert(line_items), [ { "order_id": 2, "cookie_id": 1, "quantity": 4, "extended_cost": 1.50, }, { "order_id": 2, "cookie_id": 2, "quantity": 1, "extended_cost": 4.50, }, ], ) def ship_it(order_id: int) -> None: """Версия без явной транзакции — каждый execute коммитится сам.""" s = select(line_items.c.cookie_id, line_items.c.quantity).where( line_items.c.order_id == order_id ) cookies_to_ship = connection.execute(s).fetchall() for cookie in cookies_to_ship: u = ( update(cookies) .where(cookies.c.cookie_id == cookie.cookie_id) .values(quantity=cookies.c.quantity - cookie.quantity) ) connection.execute(u) u = update(orders).where(orders.c.order_id == order_id).values(shipped=True) connection.execute(u) print(f"Shipped order ID: {order_id}") def ship_it_atomic(order_id: int) -> None: """Версия с явной транзакцией и rollback при ошибке.""" s = select(line_items.c.cookie_id, line_items.c.quantity).where( line_items.c.order_id == order_id ) with engine.begin() as conn: cookies_to_ship = conn.execute(s).fetchall() try: for cookie in cookies_to_ship: u = ( update(cookies) .where(cookies.c.cookie_id == cookie.cookie_id) .values(quantity=cookies.c.quantity - cookie.quantity) ) conn.execute(u) u = update(orders).where(orders.c.order_id == order_id).values(shipped=True) conn.execute(u) print(f"Shipped order ID: {order_id}") except IntegrityError: # with engine.begin() сам сделает rollback logging.exception("Failed to ship order %s", order_id) raise # --- используем транзакционную версию --- ship_it_atomic(1) s = select(cookies.c.cookie_name, cookies.c.quantity) with engine.connect() as conn: print(conn.execute(s).fetchall()) try: ship_it_atomic(2) 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 * Chapter 5: Reflection ** SQLAlchemy v2 :PROPERTIES: :header-args: :tangle ch5-v2.py :header-args:python: :session *ch5* :END: #+BEGIN_SRC python :results none from sqlalchemy import MetaData, create_engine metadata = MetaData() engine = create_engine("sqlite:///Chinook_Sqlite.sqlite") #+END_SRC #+BEGIN_SRC python :results table from sqlalchemy import Table artist = Table("Artist", metadata, autoload_with=engine) artist.columns.keys() #+END_SRC #+RESULTS: | ArtistId | Name | #+BEGIN_SRC python :results table from sqlalchemy import select with engine.begin() as connection: s = select(artist).limit(10) result = connection.execute(s).fetchall() result #+END_SRC #+RESULTS: | 1 | AC/DC | | 2 | Accept | | 3 | Aerosmith | | 4 | Alanis Morissette | | 5 | Alice In Chains | | 6 | Antônio Carlos Jobim | | 7 | Apocalyptica | | 8 | Audioslave | | 9 | BackBeat | | 10 | Billy Cobham | #+BEGIN_SRC python :results value album = Table('Album', metadata, autoload_with=engine) res = metadata.tables['Album'] repr(res) #+END_SRC #+RESULTS: : Table('Album', MetaData(), Column('AlbumId', INTEGER(), table=, primary_key=True, nullable=False), Column('Title', NVARCHAR(length=160), table=, nullable=False), Column('ArtistId', INTEGER(), ForeignKey('Artist.ArtistId'), table=, nullable=False), schema=None) #+BEGIN_SRC python :results value album.foreign_keys #+END_SRC #+RESULTS: : {ForeignKey('Artist.ArtistId')} *** Reflecting a Whole Database #+BEGIN_SRC python :results table metadata.reflect(bind=engine) list(metadata.tables.keys()) #+END_SRC #+RESULTS: | Artist | Album | Customer | Employee | Genre | Invoice | InvoiceLine | Track | MediaType | Playlist | PlaylistTrack | * Chapter 6: Defining Schema with SQLAlchemy ORM ** SQLAlchemy v1 :PROPERTIES: :header-args:python: :session *ch6* :tangle ch6-v1.py :header-args: :cache yes :END: #+NAME: MyBase #+BEGIN_SRC python :shebang "#!/usr/bin/env python3" :results value import re import logging from ruff_format import format_string from sqlalchemy import Table, Column, Integer, Numeric, String from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class MyBase: @classmethod def display(cls: Base) -> str: raw = repr(cls.__table__).replace(f"<{cls.__tablename__}>", f'"{cls.__tablename__}"') raw = re.sub( r"CallableColumnDefault\(\)", "NOW", raw, ) try: return format_string(raw) except Exception: logging.exception(raw) def __repr__(self) -> str: raw = f"{self.__class__.__name__}" columns = ','.join( f"{column.name}='{getattr(self, column.name)}'" for column in self.__table__.columns ) return format_string(raw + '(' + columns + ')') class Cookie(Base, MyBase): __tablename__ = "cookies" cookie_id = Column(Integer(), primary_key=True) cookie_name = Column(String(50), index=True) cookie_recipe_url = Column(String(255)) cookie_sku = Column(String(55)) quantity = Column(Integer()) unit_cost = Column(Numeric(12, 2)) Cookie.display() #+END_SRC #+RESULTS[63344a5dd32b5f4e56ae44c95a63cb48302045d9]: MyBase #+begin_example Table( "cookies", MetaData(), Column( "cookie_id", Integer(), table="cookies", primary_key=True, nullable=False, ), Column("cookie_name", String(length=50), table="cookies"), Column("cookie_recipe_url", String(length=255), table="cookies"), Column("cookie_sku", String(length=55), table="cookies"), Column("quantity", Integer(), table="cookies"), Column("unit_cost", Numeric(precision=12, scale=2), table="cookies"), schema=None, ) #+end_example #+RESULTS[5b56178ce5c3f4569e8a6b504d2403a6cd89b6d2]: #+begin_example Table( "cookies", MetaData(), Column( "cookie_id", Integer(), table="cookies", primary_key=True, nullable=False, ), Column("cookie_name", String(length=50), table="cookies"), Column("cookie_recipe_url", String(length=255), table="cookies"), Column("cookie_sku", String(length=55), table="cookies"), Column("quantity", Integer(), table="cookies"), Column("unit_cost", Numeric(precision=12, scale=2), table="cookies"), schema=None, ) #+end_example #+NAME: User #+BEGIN_SRC python :results value from datetime import datetime from sqlalchemy import DateTime class User(Base, MyBase): __tablename__ = "users" user_id = Column(Integer(), primary_key=True) username = Column(String(12), nullable=False, unique=True) email_address = Column(String(255), nullable=False) phone = Column(String(20), nullable=False) password = Column(String(25), nullable=False) created_on = Column(DateTime(), default=datetime.now) updated_on = Column(DateTime(), default=datetime.now, onupdate=datetime.now) User.display() #+END_SRC #+RESULTS[db2e3edeb658ed84112fb4858bd9142a46eff7d6]: User #+begin_example Table( "users", MetaData(), Column( "user_id", Integer(), table="users", primary_key=True, nullable=False ), Column("username", String(length=12), table="users", nullable=False), Column("email_address", String(length=255), table="users", nullable=False), Column("phone", String(length=20), table="users", nullable=False), Column("password", String(length=25), table="users", nullable=False), Column("created_on", DateTime(), table="users", default=NOW), Column("updated_on", DateTime(), table="users", onupdate=NOW, default=NOW), schema=None, ) #+end_example #+RESULTS[e9cc339461880657ed2388d4bc3210231a6ab18f]: #+begin_example Table( "users", MetaData(), Column( "user_id", Integer(), table="users", primary_key=True, nullable=False ), Column("username", String(length=12), table="users", nullable=False), Column("email_address", String(length=255), table="users", nullable=False), Column("phone", String(length=20), table="users", nullable=False), Column("password", String(length=25), table="users", nullable=False), Column("created_on", DateTime(), table="users", default=NOW), Column("updated_on", DateTime(), table="users", onupdate=NOW, default=NOW), schema=None, ) #+end_example *** Keys, Constraints, and Indexes #+BEGIN_SRC python :results none :tangle no class SomeDataClass(Base): __tablename__ = "somedataclass" __table_args__ = ( ForeignKeyConstraint("id", "other_table.id"), CheckConstraint("unit_cost >= 0.00", name="unit_cost_positive"), ) #+END_SRC *** Relationships #+NAME: Order #+BEGIN_SRC python :results value from sqlalchemy import ForeignKey, Boolean from sqlalchemy.orm import relationship, backref class Order(Base, MyBase): __tablename__ = "orders" order_id = Column(Integer(), primary_key=True) user_id = Column(Integer(), ForeignKey("users.user_id")) shipped = Column(Boolean(), default=False) user = relationship("User", backref=backref("orders", order_by=order_id)) Order.display() #+END_SRC #+RESULTS[21befa1cb4486a7e5bf8ba1e27be4391e00e421d]: Order #+begin_example Table( "orders", MetaData(), Column( "order_id", Integer(), table="orders", primary_key=True, nullable=False ), Column("user_id", Integer(), ForeignKey("users.user_id"), table="orders"), Column( "shipped", Boolean(), table="orders", default=ScalarElementColumnDefault(False), ), schema=None, ) #+end_example #+RESULTS[081b1bbdd2e372705d12cca3f6c0adc1f5c73652]: #+begin_example Table( "orders", MetaData(), Column( "order_id", Integer(), table="orders", primary_key=True, nullable=False ), Column("user_id", Integer(), ForeignKey("users.user_id"), table="orders"), Column( "shipped", Boolean(), table="orders", default=ScalarElementColumnDefault(False), ), schema=None, ) #+end_example One-to-One #+NAME: LineItem #+BEGIN_SRC python :results value class LineItem(Base, MyBase): __tablename__ = "line_items" line_item_id = Column(Integer(), primary_key=True) order_id = Column(Integer(), ForeignKey("orders.order_id")) cookie_id = Column(Integer(), ForeignKey("cookies.cookie_id")) quantity = Column(Integer()) extended_cost = Column(Numeric(12, 2)) order = relationship("Order", backref=backref("line_items", order_by=line_item_id)) cookie = relationship("Cookie", uselist=False) LineItem.display() #+END_SRC #+RESULTS[3f8b1b824259f4d103c9cd13043211def58ffb1b]: LineItem #+begin_example Table( "line_items", MetaData(), Column( "line_item_id", Integer(), table="line_items", primary_key=True, nullable=False, ), Column( "order_id", Integer(), ForeignKey("orders.order_id"), table="line_items" ), Column( "cookie_id", Integer(), ForeignKey("cookies.cookie_id"), table="line_items", ), Column("quantity", Integer(), table="line_items"), Column("extended_cost", Numeric(precision=12, scale=2), table="line_items"), schema=None, ) #+end_example #+RESULTS[0a7fb3d313c7117a29787471715c6b37d8820c1b]: #+begin_example Table( "line_items", MetaData(), Column( "line_item_id", Integer(), table="line_items", primary_key=True, nullable=False, ), Column( "order_id", Integer(), ForeignKey("orders.order_id"), table="line_items" ), Column( "cookie_id", Integer(), ForeignKey("cookies.cookie_id"), table="line_items", ), Column("quantity", Integer(), table="line_items"), Column("extended_cost", Numeric(precision=12, scale=2), table="line_items"), schema=None, ) #+end_example #+BEGIN_SRC python :results value Order.display() #+END_SRC #+RESULTS[732b625ca2ad9f696a0d2523c63a0eabb8442746]: #+begin_example Table( "orders", MetaData(), Column( "order_id", Integer(), table="orders", primary_key=True, nullable=False ), Column("user_id", Integer(), ForeignKey("users.user_id"), table="orders"), Column( "shipped", Boolean(), table="orders", default=ScalarElementColumnDefault(False), ), schema=None, ) #+end_example #+BEGIN_SRC python :results none from sqlalchemy import create_engine engine = create_engine("sqlite:///:memory:") Base.metadata.create_all(engine) #+END_SRC ** SQLAlchemy v2 :PROPERTIES: :header-args:python: :session *ch6-v2* :tangle ch6-v2.py :cache yes :END: #+BEGIN_SRC python :shebang "#!/usr/bin/env python3" :results value import re import logging from datetime import datetime from decimal import Decimal from ruff_format import format_string from sqlalchemy import ( Boolean, DateTime, ForeignKey, Integer, Numeric, String, create_engine, select, ) from sqlalchemy.orm import ( DeclarativeBase, Mapped, mapped_column, relationship, Session, ) class Base(DeclarativeBase): """Базовый класс для всех декларативных моделей.""" class MyBase: """Миксин для красивого отображения таблицы.""" @classmethod def display(cls) -> str: raw = repr(cls.__table__).replace( f"<{cls.__tablename__}>", f'"{cls.__tablename__}"' ) raw = re.sub( r"CallableColumnDefault\(\)", "NOW", raw, ) try: return format_string(raw) except Exception: logging.exception(raw) return raw class Cookie(Base, MyBase): __tablename__ = "cookies" cookie_id: Mapped[int] = mapped_column(Integer, primary_key=True) cookie_name: Mapped[str | None] = mapped_column(String(50), index=True) cookie_recipe_url: Mapped[str | None] = mapped_column(String(255)) cookie_sku: Mapped[str | None] = mapped_column(String(55)) quantity: Mapped[int | None] = mapped_column(Integer) unit_cost: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) Cookie.display() #+END_SRC #+RESULTS[f185c05d802c49b3e988a951968bc5555eb88c57]: #+begin_example Table( "cookies", MetaData(), Column( "cookie_id", Integer(), table="cookies", primary_key=True, nullable=False, ), Column("cookie_name", String(length=50), table="cookies"), Column("cookie_recipe_url", String(length=255), table="cookies"), Column("cookie_sku", String(length=55), table="cookies"), Column("quantity", Integer(), table="cookies"), Column("unit_cost", Numeric(precision=12, scale=2), table="cookies"), schema=None, ) #+end_example #+BEGIN_SRC python :results value class User(Base, MyBase): __tablename__ = "users" user_id: Mapped[int] = mapped_column(Integer, primary_key=True) username: Mapped[str] = mapped_column(String(12), nullable=False, unique=True) email_address: Mapped[str] = mapped_column(String(255), nullable=False) phone: Mapped[str] = mapped_column(String(20), nullable=False) password: Mapped[str] = mapped_column(String(25), nullable=False) created_on: Mapped[datetime] = mapped_column(DateTime, default=datetime.now) updated_on: Mapped[datetime] = mapped_column( DateTime, default=datetime.now, onupdate=datetime.now ) orders: Mapped[list["Order"]] = relationship( back_populates="user", order_by="Order.order_id" ) User.display() #+END_SRC #+RESULTS[d8d31f6b8ede872fe62231ee9e4f9380c2455209]: #+begin_example Table( "users", MetaData(), Column( "user_id", Integer(), table="users", primary_key=True, nullable=False ), Column("username", String(length=12), table="users", nullable=False), Column("email_address", String(length=255), table="users", nullable=False), Column("phone", String(length=20), table="users", nullable=False), Column("password", String(length=25), table="users", nullable=False), Column( "created_on", DateTime(), table="users", nullable=False, default=NOW ), Column( "updated_on", DateTime(), table="users", nullable=False, onupdate=NOW, default=NOW, ), schema=None, ) #+end_example *** Keys, Constraints, and Indexes #+BEGIN_SRC python :results none :tangle no from sqlalchemy import CheckConstraint, ForeignKeyConstraint class SomeDataClass(Base): __tablename__ = "somedataclass" __table_args__ = ( ForeignKeyConstraint(["id"], ["other_table.id"]), CheckConstraint("unit_cost >= 0.00", name="unit_cost_positive"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True) unit_cost: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) #+END_SRC *** Relationships #+BEGIN_SRC python :results value class Order(Base, MyBase): __tablename__ = "orders" order_id: Mapped[int] = mapped_column(Integer, primary_key=True) user_id: Mapped[int | None] = mapped_column(ForeignKey("users.user_id")) shipped: Mapped[bool | None] = mapped_column(Boolean, default=False) user: Mapped["User | None"] = relationship(back_populates="orders") line_items: Mapped[list["LineItem"]] = relationship( back_populates="order", order_by="LineItem.line_item_id" ) Order.display() #+END_SRC #+RESULTS[6ece26db13e2cf39ae98338d93d50f5139ed05a0]: #+begin_example Table( "orders", MetaData(), Column( "order_id", Integer(), table="orders", primary_key=True, nullable=False ), Column("user_id", Integer(), ForeignKey("users.user_id"), table="orders"), Column( "shipped", Boolean(), table="orders", default=ScalarElementColumnDefault(False), ), schema=None, ) #+end_example One-to-One #+BEGIN_SRC python :results value class LineItem(Base, MyBase): __tablename__ = "line_items" line_item_id: Mapped[int] = mapped_column(Integer, primary_key=True) order_id: Mapped[int | None] = mapped_column(ForeignKey("orders.order_id")) cookie_id: Mapped[int | None] = mapped_column(ForeignKey("cookies.cookie_id")) quantity: Mapped[int | None] = mapped_column(Integer) extended_cost: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) order: Mapped["Order | None"] = relationship(back_populates="line_items") cookie: Mapped["Cookie | None"] = relationship(uselist=False) LineItem.display() #+END_SRC #+RESULTS[823e900242cca3c4999b00a1efc879ea28aa5060]: #+begin_example Table( "line_items", MetaData(), Column( "line_item_id", Integer(), table="line_items", primary_key=True, nullable=False, ), Column( "order_id", Integer(), ForeignKey("orders.order_id"), table="line_items" ), Column( "cookie_id", Integer(), ForeignKey("cookies.cookie_id"), table="line_items", ), Column("quantity", Integer(), table="line_items"), Column("extended_cost", Numeric(precision=12, scale=2), table="line_items"), schema=None, ) #+end_example #+BEGIN_SRC python :results value Order.display() #+END_SRC #+RESULTS[ef90d3beef41c5187fbc5ee9f04a074da1fb310a]: #+begin_example Table( "orders", MetaData(), Column( "order_id", Integer(), table="orders", primary_key=True, nullable=False ), Column("user_id", Integer(), ForeignKey("users.user_id"), table="orders"), Column( "shipped", Boolean(), table="orders", default=ScalarElementColumnDefault(False), ), schema=None, ) #+end_example #+BEGIN_SRC python :results none engine = create_engine("sqlite:///:memory:") Base.metadata.create_all(engine) with Session(engine) as session: session.add( Cookie( cookie_name="chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe.html", cookie_sku="CC01", quantity=12, unit_cost=Decimal("0.50"), ) ) session.commit() stmt = select(Cookie).where(Cookie.cookie_name == "chocolate chip") for cookie in session.scalars(stmt): print(cookie.cookie_name, cookie.unit_cost) #+END_SRC * Chapter 7: Working with Data via SQLAlchemy ORM ** SQLAlchemy v1 :PROPERTIES: :header-args:python: :session *ch7-v1* :tangle ch7-v1.py :cache yes :END: #+BEGIN_SRC python :shebang "#!/usr/bin/env python3" :results none from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine("sqlite:///:memory:") Session = sessionmaker(bind=engine) session = Session() #+END_SRC #+BEGIN_SRC python :results none <> <> <> <> Base.metadata.create_all(engine) #+END_SRC *** Inserting Data #+BEGIN_SRC python :results value cc_cookie = Cookie( cookie_name="chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe.html", cookie_sku="CC01", quantity="12", unit_cost=0.50, ) session.add(cc_cookie) session.commit() cc_cookie.cookie_id #+END_SRC #+RESULTS[fa45196de486c5a00bb1fa04a705a018d97b5d10]: : 1 #+BEGIN_SRC python :results table dcc = Cookie( cookie_name="dark chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe/recipe_dark.html", cookie_sku="CC02", quantity=1, unit_cost=0.75, ) mol = Cookie( cookie_name="molasses", cookie_recipe_url="http://some.aweso.me/cookie/recipe_molasses.html", cookie_sku="MOL01", quantity=1, unit_cost=0.80, ) session.add(dcc) session.add(mol) session.flush() [('dcc.cookie_id', dcc.cookie_id), ('mol.cookie_id', mol.cookie_id)] #+END_SRC #+RESULTS[aa0782cad77f967ed3d7b03a7663a751a40fb13a]: | dcc.cookie_id | 2 | | mol.cookie_id | 3 | #+BEGIN_SRC python :results value c1 = Cookie( cookie_name="peanut butter", cookie_recipe_url="http://some.aweso.me/cookie/recipe/peanut.html", cookie_sku="PB01", quantity=24, unit_cost=0.25, ) c2 = Cookie( cookie_name="oatmeal raisin", cookie_recipe_url="http://some.okay.me/cookie/raisin.html", cookie_sku="EWW01", quantity=100, unit_cost=1.00, ) session.bulk_save_objects([c1, c2]) session.commit() # Тут будет None, так как для скорости делается только вставка данных, # обратно ничего не запрашивается и поэтому не добавляется в объекты c1.cookie_id #+END_SRC #+RESULTS[e11874041529f487587ec35f050a931b343524f2]: : None *** Querying Data #+BEGIN_SRC python :results output cookies = session.query(Cookie).all() print(cookies) #+END_SRC #+RESULTS[99729b5a2d9065a18497e132f4867082d30d1227]: #+begin_example [Cookie( cookie_id="1", cookie_name="chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe.html", cookie_sku="CC01", quantity="12", unit_cost="0.50", ) , Cookie( cookie_id="2", cookie_name="dark chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe/recipe_dark.html", cookie_sku="CC02", quantity="1", unit_cost="0.75", ) , Cookie( cookie_id="3", cookie_name="molasses", cookie_recipe_url="http://some.aweso.me/cookie/recipe_molasses.html", cookie_sku="MOL01", quantity="1", unit_cost="0.80", ) , Cookie( cookie_id="4", cookie_name="peanut butter", cookie_recipe_url="http://some.aweso.me/cookie/recipe/peanut.html", cookie_sku="PB01", quantity="24", unit_cost="0.25", ) , Cookie( cookie_id="5", cookie_name="oatmeal raisin", cookie_recipe_url="http://some.okay.me/cookie/raisin.html", cookie_sku="EWW01", quantity="100", unit_cost="1.00", ) ] #+end_example **** Controlling the Columns in the Query #+BEGIN_SRC python :results table session.query(Cookie.cookie_name, Cookie.quantity).first() #+END_SRC #+RESULTS: | chocolate chip | 12 | **** Ordering #+BEGIN_SRC python :results table results = [('quantity', 'cookie_name')] for cookie in session.query(Cookie).order_by(Cookie.quantity): results.append((cookie.quantity, cookie.cookie_name)) results #+END_SRC #+RESULTS[f815708c1194af5a00412df8cece6ec9db3af9ad]: | quantity | cookie_name | | 1 | dark chocolate chip | | 1 | molasses | | 12 | chocolate chip | | 24 | peanut butter | | 100 | oatmeal raisin | #+BEGIN_SRC python :results value table import pandas as pd from sqlalchemy import desc results = {'quantity': [], 'cookie_name': []} for cookie in session.query(Cookie).order_by(desc(Cookie.quantity)): results['quantity'].append(cookie.quantity) results['cookie_name'].append(cookie.cookie_name) df = pd.DataFrame(results) # Преобразуем DataFrame в список списков для Org Babel # 1. Заголовки: берем названия колонок и добавляем пустую ячейку для индекса headers = [''] + df.columns.tolist() # 2. Данные: объединяем индекс и значения строк rows = [[idx] + row.tolist() for idx, row in df.iterrows()] # 3. Собираем всё вместе: заголовки + разделитель (None) + строки summary = [headers] + [None] + rows summary #+END_SRC #+RESULTS[be902b1b955e9eadcdc6ba6670ac21a903f3f662]: | | quantity | cookie_name | |---+----------+---------------------| | 0 | 100 | oatmeal raisin | | 1 | 24 | peanut butter | | 2 | 12 | chocolate chip | | 3 | 1 | dark chocolate chip | | 4 | 1 | molasses | **** Limiting #+BEGIN_SRC python :results table query = session.query(Cookie).order_by(Cookie.quantity)[:2] # query = session.query(Cookie).order_by(Cookie.quantity).limit(2) [(result.cookie_name,) for result in query] #+END_SRC #+RESULTS[8c45f7d97da1645225462855a200559d1feaec44]: | dark chocolate chip | | molasses | **** Functions and Labels #+BEGIN_SRC python :results value from sqlalchemy import func inv_count = session.query(func.sum(Cookie.quantity)).scalar() inv_count #+END_SRC #+RESULTS[f1b621d0ce17b8f55883a46a7777bbeb162e412c]: : 138 #+BEGIN_SRC python :results raw rec_count = session.query(func.count(Cookie.cookie_name)).first() rec_count #+END_SRC #+RESULTS[10949d37140b7bcb96a26788a765d195b86950c7]: (5,) #+BEGIN_SRC python :results table rec_count = session.query( func.count(Cookie.cookie_name).label("inventory_count") ).first() (list(rec_count._mapping.keys()), (rec_count.inventory_count, )) #+END_SRC #+RESULTS[bc76d2b5448873014373602ade270610b301b86a]: | inventory_count | | 5 | **** Filtering #+BEGIN_SRC python :results value record = session.query(Cookie).filter(Cookie.cookie_name == "chocolate chip").first() record #+END_SRC #+RESULTS[00fbaff9a1d4dcb32caf86614258e147478b1a39]: : Cookie( : cookie_id="1", : cookie_name="chocolate chip", : cookie_recipe_url="http://some.aweso.me/cookie/recipe.html", : cookie_sku="CC01", : quantity="12", : unit_cost="0.50", : ) #+BEGIN_SRC python :results value record = session.query(Cookie).filter_by(cookie_name="chocolate chip").first() record #+END_SRC #+RESULTS[c00b3f6260c658d7a3e6fe76c8cb1fa00d9dd58a]: : Cookie( : cookie_id="1", : cookie_name="chocolate chip", : cookie_recipe_url="http://some.aweso.me/cookie/recipe.html", : cookie_sku="CC01", : quantity="12", : unit_cost="0.50", : ) #+BEGIN_SRC python :results table query = session.query(Cookie).filter(Cookie.cookie_name.like("chocolate chip")) [record.cookie_name for record in query] #+END_SRC #+RESULTS[498d5d0b02d3bea28799083caf1de588eccebac1]: | chocolate chip | **** Operators #+BEGIN_SRC python :results table results = session.query(Cookie.cookie_name, "SKU-" + Cookie.cookie_sku).all() [row for row in results] #+END_SRC #+RESULTS[18ddc67a0b92126197fac22eea073f2e86cf5388]: | chocolate chip | SKU-CC01 | | dark chocolate chip | SKU-CC02 | | molasses | SKU-MOL01 | | peanut butter | SKU-PB01 | | oatmeal raisin | SKU-EWW01 | #+BEGIN_SRC python :results table from sqlalchemy import cast query = session.query( Cookie.cookie_name, cast((Cookie.quantity * Cookie.unit_cost), Numeric(12, 2)).label("inv_cost"), ) [(result.cookie_name, repr(result.inv_cost)) for result in query] #+END_SRC #+RESULTS[2ce8116fbd2ec77937a191730c2536005f9ac130]: | chocolate chip | Decimal('6.00') | | dark chocolate chip | Decimal('0.75') | | molasses | Decimal('0.80') | | peanut butter | Decimal('6.00') | | oatmeal raisin | Decimal('100.00') | **** Conjunctions #+BEGIN_SRC python :table query = session.query(Cookie).filter(Cookie.quantity > 23, Cookie.unit_cost < 0.40) [(result.cookie_name,) for result in query] #+END_SRC #+RESULTS[419334c901cd5a42e1b7bf59d3ba8a8b421cca81]: | peanut butter | #+BEGIN_SRC python :table from sqlalchemy import and_, or_, not_ query = session.query(Cookie).filter( or_(Cookie.quantity.between(10, 50), Cookie.cookie_name.contains("chip")) ) [(result.cookie_name,) for result in query] #+END_SRC #+RESULTS[f1cb32abbfcf01a6b29078b44c8cddf396180df9]: | chocolate chip | | dark chocolate chip | | peanut butter | *** Updating Data #+BEGIN_SRC python :results value query = session.query(Cookie) cc_cookie = query.filter(Cookie.cookie_name == "chocolate chip").first() cc_cookie.quantity = cc_cookie.quantity + 120 session.commit() cc_cookie.quantity #+END_SRC #+RESULTS[457d95f641df08c05b2a22152eb07da84eb2d44e]: : 132 #+BEGIN_SRC python :results value query = session.query(Cookie) query = query.filter(Cookie.cookie_name == "chocolate chip") query.update({Cookie.quantity: Cookie.quantity - 20}) cc_cookie = query.first() cc_cookie.quantity #+END_SRC #+RESULTS[746058c87e4012a5ce9ced0b2378ce48f9991d8c]: : 112 *** Deleting Data #+BEGIN_SRC python :results value query = session.query(Cookie) query = query.filter(Cookie.cookie_name == "dark chocolate chip") # Гарантируем, что такая запись только одна, # и только она одна будет удалена dcc_cookie = query.one() session.delete(dcc_cookie) session.commit() dcc_cookie = query.first() # Здесь должен быть None assert dcc_cookie is None #+END_SRC #+RESULTS[61c6636ec27b672253b640a4609f3a229eec09c6]: : None #+BEGIN_SRC python :results value query = session.query(Cookie) query = query.filter(Cookie.cookie_name == "molasses") query.delete() # Убедимся, что больше такой записи нет mol_cookie = query.first() # Вернёт None assert mol_cookie is None #+END_SRC #+RESULTS[c28ab2893e1da785db9221a9f016ecaea39a7e91]: : None *** Relations #+BEGIN_SRC python :results none cookiemon = User( username="cookiemon", email_address="mon@cookie.com", phone="111-111-1111", password="password", ) cakeeater = User( username="cakeeater", email_address="cakeeater@cake.com", phone="222-222-2222", password="password", ) pieperson = User( username="pieperson", email_address="person@pie.com", phone="333-333-3333", password="password", ) session.add(cookiemon) session.add(cakeeater) session.add(pieperson) session.commit() #+END_SRC #+BEGIN_SRC python :results none o1 = Order() o1.user = cookiemon session.add(o1) cc = session.query(Cookie).filter(Cookie.cookie_name == "chocolate chip").one() line1 = LineItem(cookie=cc, quantity=2, extended_cost=1.00) pb = session.query(Cookie).filter(Cookie.cookie_name == "peanut butter").one() line2 = LineItem(quantity=12, extended_cost=3.00) line2.cookie = pb line2.order = o1 o1.line_items.append(line1) o1.line_items.append(line2) session.commit() #+END_SRC #+BEGIN_SRC python :results none o2 = Order() o2.user = cakeeater cc = session.query(Cookie).filter(Cookie.cookie_name == "chocolate chip").one() line1 = LineItem(cookie=cc, quantity=24, extended_cost=12.00) oat = session.query(Cookie).filter(Cookie.cookie_name == "oatmeal raisin").one() line2 = LineItem(cookie=oat, quantity=6, extended_cost=6.00) o2.line_items.append(line1) o2.line_items.append(line2) session.add(o2) session.commit() #+END_SRC *** Joins #+BEGIN_SRC python :results table query = ( session.query( Order.order_id, User.username, User.phone, Cookie.cookie_name, LineItem.quantity, LineItem.extended_cost, ) .join(User) .join(LineItem) .join(Cookie) .filter(User.username == "cookiemon") ) [row for row in query] #+END_SRC #+RESULTS[d7ab85e9d62ece5505b9bb871b863547945e9a2f]: | 1 | cookiemon | 111-111-1111 | chocolate chip | 2 | Decimal | (1.00) | | 1 | cookiemon | 111-111-1111 | peanut butter | 12 | Decimal | (3.00) | *** Grouping #+BEGIN_SRC python :results table query = ( session.query(User.username, func.count(Order.order_id)) .outerjoin(Order) .group_by(User.username) .all() ) query #+END_SRC #+RESULTS[0654ad9c5fb1050dbb7fe12359ee0129d974cbec]: | cakeeater | 1 | | cookiemon | 1 | | pieperson | 0 | *** Chaining #+BEGIN_SRC python :results table def get_orders_by_customer( cust_name: str, shipped: bool | None = None, details: bool = False ): query = session.query(Order.order_id, User.username, User.phone).join(User) if details: query = query.add_columns( Cookie.cookie_name, LineItem.quantity, LineItem.extended_cost ) query = query.join(LineItem).join(Cookie) if shipped is not None: query = query.where(Order.shipped == shipped) results = query.filter(User.username == cust_name).all() return results get_orders_by_customer("cakeeater") #+END_SRC #+RESULTS[2bfd4ed6da2da0894cbfff4533e06629a86a5409]: | 2 | cakeeater | 222-222-2222 | #+BEGIN_SRC python :results table get_orders_by_customer("cakeeater", details=True) #+END_SRC #+RESULTS[a16214b5f672ae8f06d8fb0d3455914cc246b4c4]: | 2 | cakeeater | 222-222-2222 | chocolate chip | 24 | Decimal | (12.00) | | 2 | cakeeater | 222-222-2222 | oatmeal raisin | 6 | Decimal | (6.00) | #+BEGIN_SRC python :results raw get_orders_by_customer("cakeeater", shipped=True) #+END_SRC #+RESULTS[17638e98cd1333daa06557954f633699643c838f]: [] #+BEGIN_SRC python :results table get_orders_by_customer("cakeeater", shipped=False) #+END_SRC #+RESULTS[9d1b1a41a3b45c33583ae66a399a093ff312b571]: | 2 | cakeeater | 222-222-2222 | #+BEGIN_SRC python :results table get_orders_by_customer("cakeeater", shipped=False, details=True) #+END_SRC #+RESULTS[39653887c276c6a3e1d1a3715346a01c5abd6cba]: | 2 | cakeeater | 222-222-2222 | chocolate chip | 24 | Decimal | (12.00) | | 2 | cakeeater | 222-222-2222 | oatmeal raisin | 6 | Decimal | (6.00) | *** Raw Queries #+BEGIN_SRC python :results output from sqlalchemy import text query = session.query(User).filter(text("username='cookiemon'")) for u in query.all(): print(u) #+END_SRC #+RESULTS[93012273f06378bfd99097513d3d305cd839c551]: : User( : user_id="1", : username="cookiemon", : email_address="mon@cookie.com", : phone="111-111-1111", : password="password", : created_on="2026-09-21 10:55:18.029598", : updated_on="2026-09-21 10:55:18.029607", : ) ** SQLAlchemy v2 #+BEGIN_SRC python :tangle "ch7-v2.py" :shebang "#!/usr/bin/env python3" :results none #!/usr/bin/env python3 """Идиоматичный SQLAlchemy 2.0 пример (по мотивам «Essential SQLAlchemy»).""" import logging import re from datetime import datetime from decimal import Decimal import pandas as pd from ruff_format import format_string from sqlalchemy import ( Boolean, DateTime, ForeignKey, Integer, Numeric, String, create_engine, delete, func, insert, or_, select, text, update, ) from sqlalchemy.orm import ( DeclarativeBase, Mapped, Session, mapped_column, relationship, ) # --------------------------------------------------------------------------- # Engine # --------------------------------------------------------------------------- engine = create_engine("sqlite:///:memory:", echo=False) # --------------------------------------------------------------------------- # Базовый класс с удобным repr и display # --------------------------------------------------------------------------- class Base(DeclarativeBase): """Базовый класс с repr() и display() для схемы таблицы.""" def __repr__(self) -> str: raw = f"{self.__class__.__name__}" columns = ",".join( f"{col.name}={getattr(self, col.name)!r}" for col in self.__table__.columns ) return format_string(f"{raw}({columns})") @classmethod def display(cls) -> str: raw = repr(cls.__table__).replace( f"<{cls.__tablename__}>", f'"{cls.__tablename__}"' ) raw = re.sub( r"CallableColumnDefault\(\)", "NOW", raw, ) try: return format_string(raw) except Exception: logging.exception(raw) return raw # --------------------------------------------------------------------------- # Модели # --------------------------------------------------------------------------- class Cookie(Base): __tablename__ = "cookies" cookie_id: Mapped[int] = mapped_column(primary_key=True) cookie_name: Mapped[str | None] = mapped_column(String(50), index=True) cookie_recipe_url: Mapped[str | None] = mapped_column(String(255)) cookie_sku: Mapped[str | None] = mapped_column(String(55)) quantity: Mapped[int | None] = mapped_column(Integer()) unit_cost: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) class User(Base): __tablename__ = "users" user_id: Mapped[int] = mapped_column(primary_key=True) username: Mapped[str] = mapped_column(String(12), nullable=False, unique=True) email_address: Mapped[str] = mapped_column(String(255), nullable=False) phone: Mapped[str] = mapped_column(String(20), nullable=False) password: Mapped[str] = mapped_column(String(25), nullable=False) created_on: Mapped[datetime] = mapped_column(DateTime, default=datetime.now) updated_on: Mapped[datetime] = mapped_column( DateTime, default=datetime.now, onupdate=datetime.now ) class Order(Base): __tablename__ = "orders" order_id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int | None] = mapped_column(ForeignKey("users.user_id")) shipped: Mapped[bool] = mapped_column(Boolean, default=False) user: Mapped["User"] = relationship(back_populates="orders") line_items: Mapped[list["LineItem"]] = relationship(back_populates="order") class LineItem(Base): __tablename__ = "line_items" line_item_id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int | None] = mapped_column(ForeignKey("orders.order_id")) cookie_id: Mapped[int | None] = mapped_column(ForeignKey("cookies.cookie_id")) quantity: Mapped[int | None] = mapped_column(Integer()) extended_cost: Mapped[Decimal | None] = mapped_column(Numeric(12, 2)) order: Mapped["Order"] = relationship(back_populates="line_items") cookie: Mapped["Cookie"] = relationship(uselist=False) # Дополняем User.orders после объявления Order (back_populates с обеих сторон) User.orders = relationship("Order", back_populates="user", order_by="Order.order_id") # --------------------------------------------------------------------------- # Создание схемы # --------------------------------------------------------------------------- Base.metadata.create_all(engine) Cookie.display() User.display() Order.display() LineItem.display() # --------------------------------------------------------------------------- # Работа с сессией # --------------------------------------------------------------------------- with Session(engine) as session: # ======================================================================= # INSERT: одиночные объекты через ORM # ======================================================================= cc_cookie = Cookie( cookie_name="chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe.html", cookie_sku="CC01", quantity=12, unit_cost=Decimal("0.50"), ) session.add(cc_cookie) session.commit() print("cc_cookie.cookie_id =", cc_cookie.cookie_id) dcc = Cookie( cookie_name="dark chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe/recipe_dark.html", cookie_sku="CC02", quantity=1, unit_cost=Decimal("0.75"), ) mol = Cookie( cookie_name="molasses", cookie_recipe_url="http://some.aweso.me/cookie/recipe_molasses.html", cookie_sku="MOL01", quantity=1, unit_cost=Decimal("0.80"), ) session.add_all([dcc, mol]) session.flush() print([("dcc.cookie_id", dcc.cookie_id), ("mol.cookie_id", mol.cookie_id)]) # ======================================================================= # INSERT: bulk-вставка (замена legacy bulk_save_objects) # ======================================================================= session.execute( insert(Cookie), [ { "cookie_name": "peanut butter", "cookie_recipe_url": "http://some.aweso.me/cookie/recipe/peanut.html", "cookie_sku": "PB01", "quantity": 24, "unit_cost": Decimal("0.25"), }, { "cookie_name": "oatmeal raisin", "cookie_recipe_url": "http://some.okay.me/cookie/raisin.html", "cookie_sku": "EWW01", "quantity": 100, "unit_cost": Decimal("1.00"), }, ], ) session.commit() # Пример с .returning() — если нужны обратно PK/имена result = session.execute( insert(Cookie).returning(Cookie.cookie_id, Cookie.cookie_name), [ { "cookie_name": "sugar", "cookie_recipe_url": "http://example.com/sugar", "cookie_sku": "SGR01", "quantity": 50, "unit_cost": Decimal("0.40"), }, ], ) print("inserted (returning):", result.all()) session.commit() # ======================================================================= # SELECT: базовые # ======================================================================= cookies = session.scalars(select(Cookie)).all() print(cookies) row = session.execute(select(Cookie.cookie_name, Cookie.quantity)).first() print(row) # ======================================================================= # DataFrame из select() # ======================================================================= stmt = select(Cookie.quantity, Cookie.cookie_name).order_by(Cookie.quantity.desc()) rows = session.execute(stmt).all() df = pd.DataFrame(rows, columns=["quantity", "cookie_name"]) headers = [""] + df.columns.tolist() data_rows = [[idx, *row] for idx, row in zip(df.index, df.to_numpy().tolist())] summary = [headers, None, *data_rows] print(summary) # ======================================================================= # LIMIT # ======================================================================= stmt = select(Cookie).order_by(Cookie.quantity).limit(2) print([(c.cookie_name,) for c in session.scalars(stmt)]) # ======================================================================= # Агрегаты # ======================================================================= inv_count = session.scalar(select(func.sum(Cookie.quantity))) print("inv_count =", inv_count) rec = session.execute( select(func.count(Cookie.cookie_name).label("inventory_count")) ).one() print((list(rec._mapping.keys()), (rec.inventory_count,))) # ======================================================================= # Фильтры # ======================================================================= record = session.scalars( select(Cookie).where(Cookie.cookie_name == "chocolate chip") ).first() print(record) record = session.scalars( select(Cookie).filter_by(cookie_name="chocolate chip") ).first() print(record) stmt = select(Cookie).where(Cookie.cookie_name.like("chocolate chip")) print([c.cookie_name for c in session.scalars(stmt)]) # ======================================================================= # Вычисляемые колонки # ======================================================================= stmt = select(Cookie.cookie_name, ("SKU-" + Cookie.cookie_sku).label("sku")) for r in session.execute(stmt): print(r) stmt = select( Cookie.cookie_name, func.cast(Cookie.quantity * Cookie.unit_cost, Numeric(12, 2)).label("inv_cost"), ) print([(r.cookie_name, repr(r.inv_cost)) for r in session.execute(stmt)]) stmt = select(Cookie).where( Cookie.quantity > 23, Cookie.unit_cost < Decimal("0.40") ) print([(c.cookie_name,) for c in session.scalars(stmt)]) stmt = select(Cookie).where( or_( Cookie.quantity.between(10, 50), Cookie.cookie_name.contains("chip"), ) ) print([(c.cookie_name,) for c in session.scalars(stmt)]) # ======================================================================= # UPDATE (ORM-объект) # ======================================================================= cc_cookie = session.scalars( select(Cookie).filter_by(cookie_name="chocolate chip") ).first() assert cc_cookie is not None cc_cookie.quantity = (cc_cookie.quantity or 0) + 120 session.commit() print("cc_cookie.quantity =", cc_cookie.quantity) # UPDATE (bulk) session.execute( update(Cookie) .where(Cookie.cookie_name == "chocolate chip") .values(quantity=Cookie.quantity - 20) ) session.commit() print( "after bulk update:", session.scalars( select(Cookie.quantity).where(Cookie.cookie_name == "chocolate chip") ).one(), ) # ======================================================================= # DELETE (ORM) # ======================================================================= dcc_cookie = session.scalars( select(Cookie).filter_by(cookie_name="dark chocolate chip") ).one() session.delete(dcc_cookie) session.commit() assert ( session.scalars( select(Cookie).filter_by(cookie_name="dark chocolate chip") ).first() is None ) # DELETE (bulk) session.execute(delete(Cookie).where(Cookie.cookie_name == "molasses")) session.commit() assert ( session.scalars(select(Cookie).filter_by(cookie_name="molasses")).first() is None ) # ======================================================================= # Пользователи и связи # ======================================================================= cookiemon = User( username="cookiemon", email_address="mon@cookie.com", phone="111-111-1111", password="password", ) cakeeater = User( username="cakeeater", email_address="cakeeater@cake.com", phone="222-222-2222", password="password", ) pieperson = User( username="pieperson", email_address="person@pie.com", phone="333-333-3333", password="password", ) session.add_all([cookiemon, cakeeater, pieperson]) session.commit() o1 = Order() o1.user = cookiemon session.add(o1) cc = session.scalars( select(Cookie).filter_by(cookie_name="chocolate chip") ).one() pb = session.scalars( select(Cookie).filter_by(cookie_name="peanut butter") ).one() line1 = LineItem(cookie=cc, quantity=2, extended_cost=Decimal("1.00")) line2 = LineItem(quantity=12, extended_cost=Decimal("3.00")) line2.cookie = pb line2.order = o1 o1.line_items.append(line1) o1.line_items.append(line2) session.commit() o2 = Order() o2.user = cakeeater line1 = LineItem(cookie=cc, quantity=24, extended_cost=Decimal("12.00")) oat = session.scalars( select(Cookie).filter_by(cookie_name="oatmeal raisin") ).one() line2 = LineItem(cookie=oat, quantity=6, extended_cost=Decimal("6.00")) o2.line_items.append(line1) o2.line_items.append(line2) session.add(o2) session.commit() # ======================================================================= # JOIN # ======================================================================= stmt = ( select( Order.order_id, User.username, User.phone, Cookie.cookie_name, LineItem.quantity, LineItem.extended_cost, ) .join(User) .join(LineItem) .join(Cookie) .where(User.username == "cookiemon") ) print([tuple(r) for r in session.execute(stmt)]) # ======================================================================= # GROUP BY + OUTER JOIN # ======================================================================= stmt = ( select(User.username, func.count(Order.order_id)) .outerjoin(Order) .group_by(User.username) ) print(session.execute(stmt).all()) # ======================================================================= # Функция-обёртка # ======================================================================= def get_orders_by_customer( cust_name: str, shipped: bool | None = None, details: bool = False, ): stmt = select(Order.order_id, User.username, User.phone).join(User) if details: stmt = ( stmt.add_columns( Cookie.cookie_name, LineItem.quantity, LineItem.extended_cost, ) .join(LineItem) .join(Cookie) ) if shipped is not None: stmt = stmt.where(Order.shipped == shipped) stmt = stmt.where(User.username == cust_name) return session.execute(stmt).all() print(get_orders_by_customer("cakeeater")) print(get_orders_by_customer("cakeeater", details=True)) print(get_orders_by_customer("cakeeater", shipped=True)) print(get_orders_by_customer("cakeeater", shipped=False)) print(get_orders_by_customer("cakeeater", shipped=False, details=True)) # ======================================================================= # Текстовый SQL # ======================================================================= stmt = select(User).where(text("username='cookiemon'")) for u in session.scalars(stmt): print(u) #+END_SRC * Chapter 8: Understanding the Session and Exceptions - Transient :: объект *не находится в сессии*, ровно как и *не находится в базе данных* - Pending :: объект был *добавлен в сессию* методом /add()/, но ещё не был закоммичен - Persistent :: объкт *в сессии* и *содержит соответствующую запись в базе* - Detached :: объект *больше не в сессии*, но *содержит запись в базе данных* ** SQLAlchemy v1 :PROPERTIES: :header-args:python: :session *ch8-v1* :tangle ch8-v1.py :cache yes :noweb yes :END: #+BEGIN_SRC python :shebang "#!/usr/bin/env python3" :results none from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine("sqlite:///:memory:") Session = sessionmaker(bind=engine) session = Session() <> <> <> <> Base.metadata.create_all(engine) #+END_SRC *** The SQLALchemy Session #+BEGIN_SRC python :results none from sqlalchemy import inspect cc_cookie = Cookie( cookie_name="chocolate chip", cookie_recipe_url="http://some.aweso.me/cookie/recipe.html", cookie_sku="CC01", quantity="12", unit_cost=0.50, ) #+END_SRC #+NAME: inspect_state #+BEGIN_SRC python :results table insp = inspect(cc_cookie) [ (state, getattr(insp, state)) for state in ["transient", "pending", "persistent", "detached"] ] #+END_SRC #+RESULTS[5b92b5e6a2e5ebbe508787b0f819dcb13c3dac91]: inspect_state | transient | True | | pending | False | | persistent | False | | detached | False | #+BEGIN_SRC python :results table session.add(cc_cookie) <> #+END_SRC #+RESULTS[0378d1b2c44b7e1c975841f7ed5c2996a7549d8f]: | transient | False | | pending | True | | persistent | False | | detached | False | #+BEGIN_SRC python :results table session.commit() <> #+END_SRC #+RESULTS[7ee412ff6ac55f300185fc0b8c2253bba4975e13]: | transient | False | | pending | False | | persistent | True | | detached | False | #+BEGIN_SRC python :results table session.expunge(cc_cookie) <> #+END_SRC #+RESULTS[3b976cab1dcea5b45840e1343035aec8a9737cdc]: | transient | False | | pending | False | | persistent | False | | detached | True | #+BEGIN_SRC python :results value session.add(cc_cookie) cc_cookie.cookie_name = "Change chocolate chip" insp.modified #+END_SRC #+RESULTS[d5921158c04975974281520c2b1b2eb22ea396b4]: : True #+BEGIN_SRC python :results output for attr, attr_state in insp.attrs.items(): if attr_state.history.has_changes(): print('{}: {}'.format(attr, attr_state.value)) print('History: {}\n'.format(attr_state.history)) #+END_SRC #+RESULTS[6e8df47d932c957791f332b1e631530a757fbbff]: : cookie_name: Change chocolate chip : History: History(added=['Change chocolate chip'], unchanged=(), deleted=()) *** Exceptions **** MultipleResultsFound #+BEGIN_SRC python :results output import logging import sqlalchemy dcc = Cookie( 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, ) session.add(dcc) session.commit() try: results = session.query(Cookie).one() except sqlalchemy.exc.MultipleResultsFound: logging.exception("более одной записи в таблице Cookie") #+END_SRC #+RESULTS[dca1360ecf2861d3a9f15154a7cfcb97d2ebf854]: #+begin_example ERROR:root:более одной записи в таблице Cookie Traceback (most recent call last): File "/tmp/babel-0NE78C/python-ZcRDHC", line 14, in results = session.query(Cookie).one() File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/query.py", line 2815, in one return self._iter().one() # type: ignore ~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/result.py", line 1867, in one return self._only_one_row( ~~~~~~~~~~~~~~~~~~^ raise_for_second_row=True, raise_for_none=True, scalar=False ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/result.py", line 852, in _only_one_row raise exc.MultipleResultsFound( ...<4 lines>... ) sqlalchemy.exc.MultipleResultsFound: Multiple rows were found when exactly one was required #+end_example **** NoResultFound #+BEGIN_SRC python :results output try: session.query(Order).one() except sqlalchemy.exc.NoResultFound: logging.exception("нет записей в таблице Order") #+END_SRC #+RESULTS: #+begin_example ERROR:root:нет записей в таблице Order Traceback (most recent call last): File "/tmp/babel-0NE78C/python-gFJXyQ", line 2, in session.query(Order).one() ~~~~~~~~~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/query.py", line 2815, in one return self._iter().one() # type: ignore ~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/result.py", line 1867, in one return self._only_one_row( ~~~~~~~~~~~~~~~~~~^ raise_for_second_row=True, raise_for_none=True, scalar=False ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/result.py", line 799, in _only_one_row raise exc.NoResultFound( "No row was found when one was required" ) sqlalchemy.exc.NoResultFound: No row was found when one was required #+end_example **** DetachedInstanceError #+BEGIN_SRC python :results output cookiemon = User( username="cookiemon", email_address="mon@cookie.com", phone="111-111-1111", password="password", ) session.add(cookiemon) o1 = Order() o1.user = cookiemon session.add(o1) cc = session.query(Cookie).filter(Cookie.cookie_name == "Change chocolate chip").one() line1 = LineItem(order=o1, cookie=cc, quantity=2, extended_cost=1.00) session.add(line1) session.commit() order = session.query(Order).first() session.expunge(order) try: order.line_items except sqlalchemy.orm.exc.DetachedInstanceError: logging.exception("order уже отвязан от сессии, нельзя получить его relationship") #+END_SRC #+RESULTS: #+begin_example ERROR:root:order уже отвязан от сессии, нельзя получить его relationship Traceback (most recent call last): File "/tmp/babel-0NE78C/python-K8DLx5", line 22, in order.line_items File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/attributes.py", line 569, in __get__ return self.impl.get(state, dict_) # type: ignore[no-any-return] ~~~~~~~~~~~~~^^^^^^^^^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/attributes.py", line 1096, in get value = self._fire_loader_callables(state, key, passive) File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/attributes.py", line 1131, in _fire_loader_callables return self.callable_(state, passive) ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/strategies.py", line 929, in _load_for_state raise orm_exc.DetachedInstanceError( ...<3 lines>... ) sqlalchemy.orm.exc.DetachedInstanceError: Parent instance is not bound to a Session; lazy load operation of attribute 'line_items' cannot proceed (Background on this error at: https://sqlalche.me/e/20/bhk3) #+end_example *** Transactions :PROPERTIES: :header-args:python: :session *ch8-tr-v1* :tangle ch8-tr-v1.py :cache yes :noweb yes :END: #+BEGIN_SRC python :results none :shebang "#!/usr/bin/env python3" import sqlalchemy as sa from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine("sqlite:///:memory:") Session = sessionmaker(bind=engine) session = Session() from datetime import datetime from sqlalchemy import ( Boolean, CheckConstraint, Column, DateTime, ForeignKey, Integer, Numeric, String, Table, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import backref, relationship Base = declarative_base() class Cookie(Base): __tablename__ = "cookies" __table_args__ = (CheckConstraint("quantity >= 0", name="quantity_positive"),) cookie_id = Column(Integer, primary_key=True) cookie_name = Column(String(50), index=True) cookie_recipe_url = Column(String(255)) cookie_sku = Column(String(55)) quantity = Column(Integer()) unit_cost = Column(Numeric(12, 2)) def __init__(self, name, recipe_url=None, sku=None, quantity=0, unit_cost=0.00): self.cookie_name = name self.cookie_recipe_url = recipe_url self.cookie_sku = sku self.quantity = quantity self.unit_cost = unit_cost def __repr__(self): return ( "Cookie(cookie_name='{self.cookie_name}', " "cookie_recipe_url='{self.cookie_recipe_url}', " "cookie_sku='{self.cookie_sku}', " "quantity={self.quantity}, " "unit_cost={self.unit_cost})".format(self=self) ) class User(Base): __tablename__ = "users" user_id = Column(Integer(), primary_key=True) username = Column(String(15), nullable=False, unique=True) email_address = Column(String(255), nullable=False) phone = Column(String(20), nullable=False) password = Column(String(25), nullable=False) created_on = Column(DateTime(), default=datetime.now) updated_on = Column(DateTime(), default=datetime.now, onupdate=datetime.now) def __init__(self, username, email_address, phone, password): self.username = username self.email_address = email_address self.phone = phone self.password = password def __repr__(self): return ( "User(username='{self.username}', " "email_address='{self.email_address}', " "phone='{self.phone}', " "password='{self.password}')".format(self=self) ) class Order(Base): __tablename__ = "orders" order_id = Column(Integer(), primary_key=True) user_id = Column(Integer(), ForeignKey("users.user_id")) shipped = Column(Boolean(), default=False) user = relationship("User", backref=backref("orders", order_by=order_id)) def __repr__(self): return "Order(user_id={self.user_id}, " "shipped={self.shipped})".format( self=self ) class LineItem(Base): __tablename__ = "line_items" line_item_id = Column(Integer(), primary_key=True) order_id = Column(Integer(), ForeignKey("orders.order_id")) cookie_id = Column(Integer(), ForeignKey("cookies.cookie_id")) quantity = Column(Integer()) extended_cost = Column(Numeric(12, 2)) order = relationship("Order", backref=backref("line_items", order_by=line_item_id)) cookie = relationship("Cookie", uselist=False) def __repr__(self): return ( "LineItem(order_id={self.order_id}, " "cookie_id={self.cookie_id}, " "quantity={self.quantity}, " "extended_cost={self.extended_cost})".format(self=self) ) Base.metadata.create_all(engine) cookiemon = User("cookiemon", "mon@cookie.com", "111-111-1111", "password") cc = Cookie( "chocolate chip", "http://some.aweso.me/cookie/recipe.html", "CC01", 12, 0.50 ) dcc = Cookie( "dark chocolate chip", "http://some.aweso.me/cookie/recipe_dark.html", "CC02", 1, 0.75, ) session.add(cookiemon) session.add(cc) session.add(dcc) o1 = Order() o1.user = cookiemon session.add(o1) line1 = LineItem(order=o1, cookie=cc, quantity=9, extended_cost=4.50) session.add(line1) session.commit() o2 = Order() o2.user = cookiemon session.add(o2) line1 = LineItem(order=o2, cookie=cc, quantity=2, extended_cost=1.50) line2 = LineItem(order=o2, cookie=dcc, quantity=9, extended_cost=6.75) session.add(line1) session.add(line2) session.commit() def ship_it(order_id: str) -> None: """ принимает order_id, удаляет cookies из доступного числа, помечает заказ как shipped """ order = session.query(Order).get(order_id) for li in order.line_items: li.cookie.quantity = li.cookie.quantity - li.quantity session.add(li.cookie) order.shipped = True session.add(order) session.commit() print(f"shipped order ID: {order_id}") #+END_SRC #+BEGIN_SRC python :results output ship_it(1) print(session.query(Cookie.cookie_name, Cookie.quantity).all()) #+END_SRC #+RESULTS: : shipped order ID: 1 : [('chocolate chip', 3), ('dark chocolate chip', 1)] #+BEGIN_SRC python :results output import logging try: ship_it(2) except sa.exc.IntegrityError: logging.exception("order_id: 2") #+END_SRC #+RESULTS: #+begin_example ERROR:root:order_id: 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-0NE78C/python-y32EFG", line 4, in ship_it(2) ~~~~~~~^^^ File "", line 156, in ship_it File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2035, in commit trans.commit(_to_root=True) ~~~~~~~~~~~~^^^^^^^^^^^^^^^ File "", line 2, in commit File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go ret_value = fn(self, *arg, **kw) File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 1316, in commit self._prepare_impl() ~~~~~~~~~~~~~~~~~~^^ File "", line 2, in _prepare_impl File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go ret_value = fn(self, *arg, **kw) File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 1290, in _prepare_impl self.session.flush() ~~~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 4353, in flush self._flush(objects) ~~~~~~~~~~~^^^^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 4488, in _flush with util.safe_reraise(): ~~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/util/langhelpers.py", line 122, in __exit__ raise exc_value.with_traceback(exc_tb) File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 4449, in _flush flush_context.execute() ~~~~~~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/unitofwork.py", line 465, in execute rec.execute(self) ~~~~~~~~~~~^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/unitofwork.py", line 641, in execute util.preloaded.orm_persistence.save_obj( ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ self.mapper, ^^^^^^^^^^^^ uow.states_for_mapper_hierarchy(self.mapper, False, False), ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ uow, ^^^^ ) ^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/persistence.py", line 86, in save_obj _emit_update_statements( ~~~~~~~~~~~~~~~~~~~~~~~^ base_mapper, ^^^^^^^^^^^^ ...<3 lines>... update, ^^^^^^^ ) ^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/persistence.py", line 913, in _emit_update_statements c = connection.execute( statement, multiparams, execution_options=execution_options ) 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=? WHERE cookies.cookie_id = ?] [parameters: (-8, 2)] (Background on this error at: https://sqlalche.me/e/20/gkpj) #+end_example #+BEGIN_SRC python :results output try: print(session.query(Cookie.cookie_name, Cookie.quantity).all()) except sa.exc.InvalidRequestError: logging.exception("нужен rollback") #+END_SRC #+RESULTS: #+begin_example ERROR:root:нужен rollback Traceback (most recent call last): File "/tmp/babel-0NE78C/python-aSjO6i", line 2, in print(session.query(Cookie.cookie_name, Cookie.quantity).all()) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/query.py", line 2711, in all return self._iter().all() # type: ignore ~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/query.py", line 2864, in _iter result: Union[ScalarResult[_T], Result[_T]] = self.session.execute( ~~~~~~~~~~~~~~~~~~~~^ statement, ^^^^^^^^^^ params, ^^^^^^^ execution_options={"_sa_orm_load_options": self.load_options}, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2373, in execute return self._execute_internal( ~~~~~~~~~~~~~~~~~~~~~~^ statement, ^^^^^^^^^^ ...<4 lines>... _add_event=_add_event, ^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2261, in _execute_internal conn = self._connection_for_bind(bind) File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2113, in _connection_for_bind return trans._connection_for_bind(engine, execution_options) ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "", line 2, in _connection_for_bind File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/state_changes.py", line 101, in _go self._raise_for_prerequisite_state(fn.__name__, current_state) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 975, in _raise_for_prerequisite_state raise sa_exc.PendingRollbackError( ...<6 lines>... ) sqlalchemy.exc.PendingRollbackError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: (sqlite3.IntegrityError) CHECK constraint failed: quantity_positive [SQL: UPDATE cookies SET quantity=? WHERE cookies.cookie_id = ?] [parameters: (-8, 2)] (Background on this error at: https://sqlalche.me/e/20/gkpj) (Background on this error at: https://sqlalche.me/e/20/7s2a) #+end_example #+BEGIN_SRC python :results output session.rollback() print(session.query(Cookie.cookie_name, Cookie.quantity).all()) #+END_SRC #+RESULTS: : [('chocolate chip', 3), ('dark chocolate chip', 1)] #+BEGIN_SRC python :results output def trans_ship_it (order_id:str) -> None: """ship_it с поддержкой отката транзакции""" order = session.query(Order).get(order_id) for li in order.line_items: li.cookie.quantity = li.cookie.quantity - li.quantity session.add(li.cookie) order.shipped = True session.add(order) try: session.commit() print(f"shipped order ID: {order_id}") except sa.exc.IntegrityError as error: logging.error(error.orig) session.rollback() trans_ship_it(2) print(session.query(Cookie.cookie_name, Cookie.quantity).all()) #+END_SRC #+RESULTS: : ERROR:root:CHECK constraint failed: quantity_positive : [('chocolate chip', 3), ('dark chocolate chip', 1)] ** SQLAlchemy v2 *** Transactions :PROPERTIES: :header-args:python: :session *ch8-tr-v2* :tangle ch8-tr-v2.py :cache yes :END: #+BEGIN_SRC python :results output :shebang "#!/usr/bin/env python3" #!/usr/bin/env python3 import logging from datetime import datetime from decimal import Decimal import sqlalchemy as sa from sqlalchemy import ( Boolean, CheckConstraint, DateTime, ForeignKey, Integer, Numeric, String, create_engine, select, ) from sqlalchemy.orm import ( DeclarativeBase, Mapped, Session, mapped_column, relationship, sessionmaker, ) engine = create_engine("sqlite:///:memory:") SessionLocal = sessionmaker(bind=engine) session = SessionLocal() class Base(DeclarativeBase): pass class Cookie(Base): __tablename__ = "cookies" __table_args__ = (CheckConstraint("quantity >= 0", name="quantity_positive"),) cookie_id: Mapped[int] = mapped_column(primary_key=True) cookie_name: Mapped[str] = mapped_column(String(50), index=True) cookie_recipe_url: Mapped[str | None] = mapped_column(String(255)) cookie_sku: Mapped[str | None] = mapped_column(String(55)) quantity: Mapped[int] = mapped_column(Integer(), default=0) unit_cost: Mapped[Decimal] = mapped_column(Numeric(12, 2), default=Decimal("0.00")) def __init__( self, name: str, recipe_url: str | None = None, sku: str | None = None, quantity: int = 0, unit_cost: float | Decimal = 0.00, ) -> None: self.cookie_name = name self.cookie_recipe_url = recipe_url self.cookie_sku = sku self.quantity = quantity self.unit_cost = Decimal(str(unit_cost)) def __repr__(self) -> str: return ( f"Cookie(cookie_name='{self.cookie_name}', " f"cookie_recipe_url='{self.cookie_recipe_url}', " f"cookie_sku='{self.cookie_sku}', " f"quantity={self.quantity}, " f"unit_cost={self.unit_cost})" ) class User(Base): __tablename__ = "users" user_id: Mapped[int] = mapped_column(primary_key=True) username: Mapped[str] = mapped_column(String(15), nullable=False, unique=True) email_address: Mapped[str] = mapped_column(String(255), nullable=False) phone: Mapped[str] = mapped_column(String(20), nullable=False) password: Mapped[str] = mapped_column(String(25), nullable=False) created_on: Mapped[datetime] = mapped_column(DateTime(), default=datetime.now) updated_on: Mapped[datetime] = mapped_column( DateTime(), default=datetime.now, onupdate=datetime.now ) orders: Mapped[list["Order"]] = relationship( back_populates="user", order_by="Order.order_id" ) def __init__( self, username: str, email_address: str, phone: str, password: str ) -> None: self.username = username self.email_address = email_address self.phone = phone self.password = password def __repr__(self) -> str: return ( f"User(username='{self.username}', " f"email_address='{self.email_address}', " f"phone='{self.phone}', " f"password='{self.password}')" ) class Order(Base): __tablename__ = "orders" order_id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int | None] = mapped_column(ForeignKey("users.user_id")) shipped: Mapped[bool] = mapped_column(Boolean(), default=False) user: Mapped["User | None"] = relationship(back_populates="orders") line_items: Mapped[list["LineItem"]] = relationship( back_populates="order", order_by="LineItem.line_item_id" ) def __repr__(self) -> str: return f"Order(user_id={self.user_id}, shipped={self.shipped})" class LineItem(Base): __tablename__ = "line_items" line_item_id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int | None] = mapped_column(ForeignKey("orders.order_id")) cookie_id: Mapped[int | None] = mapped_column(ForeignKey("cookies.cookie_id")) quantity: Mapped[int] = mapped_column(Integer()) extended_cost: Mapped[Decimal] = mapped_column(Numeric(12, 2)) order: Mapped["Order | None"] = relationship(back_populates="line_items") cookie: Mapped["Cookie | None"] = relationship(uselist=False) def __repr__(self) -> str: return ( f"LineItem(order_id={self.order_id}, " f"cookie_id={self.cookie_id}, " f"quantity={self.quantity}, " f"extended_cost={self.extended_cost})" ) Base.metadata.create_all(engine) cookiemon = User("cookiemon", "mon@cookie.com", "111-111-1111", "password") cc = Cookie( "chocolate chip", "http://some.aweso.me/cookie/recipe.html", "CC01", 12, 0.50 ) dcc = Cookie( "dark chocolate chip", "http://some.aweso.me/cookie/recipe_dark.html", "CC02", 1, 0.75, ) session.add(cookiemon) session.add(cc) session.add(dcc) o1 = Order() o1.user = cookiemon session.add(o1) line1 = LineItem(order=o1, cookie=cc, quantity=9, extended_cost=4.50) session.add(line1) session.commit() o2 = Order() o2.user = cookiemon session.add(o2) line1 = LineItem(order=o2, cookie=cc, quantity=2, extended_cost=1.50) line2 = LineItem(order=o2, cookie=dcc, quantity=9, extended_cost=6.75) session.add(line1) session.add(line2) session.commit() def ship_it(order_id: int) -> None: """ принимает order_id, удаляет cookies из доступного числа, помечает заказ как shipped """ order = session.get(Order, order_id) if order is None: raise ValueError(f"Order {order_id} not found") for li in order.line_items: assert li.cookie is not None li.cookie.quantity = li.cookie.quantity - li.quantity session.add(li.cookie) order.shipped = True session.add(order) session.commit() print(f"shipped order ID: {order_id}") ship_it(1) stmt = select(Cookie.cookie_name, Cookie.quantity) print(session.execute(stmt).all()) try: ship_it(2) except sa.exc.IntegrityError: logging.exception("order_id: 2") try: print(session.execute(stmt).all()) except sa.exc.InvalidRequestError: logging.exception("нужен rollback") session.rollback() print(session.execute(stmt).all()) def trans_ship_it(order_id: int) -> None: try: with session.begin_nested(): order = session.get(Order, order_id) if order is None: raise ValueError(f"Order {order_id} not found") for li in order.line_items: assert li.cookie is not None li.cookie.quantity -= li.quantity order.shipped = True print(f"shipped order ID: {order_id}") except sa.exc.IntegrityError as error: logging.error(error.orig) trans_ship_it(2) print(session.execute(stmt).all()) #+END_SRC #+RESULTS[6b80bda7ad50095f9d5b584ed9e5f8e557b0dfd5]: #+begin_example shipped order ID: 1 [('chocolate chip', 3), ('dark chocolate chip', 1)] ERROR:root:order_id: 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-0NE78C/python-uG2oyE", line 201, in ship_it(2) ~~~~~~~^^^ File "/tmp/babel-0NE78C/python-uG2oyE", line 191, in ship_it session.commit() ~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2035, in commit trans.commit(_to_root=True) ~~~~~~~~~~~~^^^^^^^^^^^^^^^ File "", line 2, in commit File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go ret_value = fn(self, *arg, **kw) File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 1316, in commit self._prepare_impl() ~~~~~~~~~~~~~~~~~~^^ File "", line 2, in _prepare_impl File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go ret_value = fn(self, *arg, **kw) File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 1290, in _prepare_impl self.session.flush() ~~~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 4353, in flush self._flush(objects) ~~~~~~~~~~~^^^^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 4488, in _flush with util.safe_reraise(): ~~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/util/langhelpers.py", line 122, in __exit__ raise exc_value.with_traceback(exc_tb) File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 4449, in _flush flush_context.execute() ~~~~~~~~~~~~~~~~~~~~~^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/unitofwork.py", line 465, in execute rec.execute(self) ~~~~~~~~~~~^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/unitofwork.py", line 641, in execute util.preloaded.orm_persistence.save_obj( ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ self.mapper, ^^^^^^^^^^^^ uow.states_for_mapper_hierarchy(self.mapper, False, False), ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ uow, ^^^^ ) ^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/persistence.py", line 86, in save_obj _emit_update_statements( ~~~~~~~~~~~~~~~~~~~~~~~^ base_mapper, ^^^^^^^^^^^^ ...<3 lines>... update, ^^^^^^^ ) ^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/persistence.py", line 913, in _emit_update_statements c = connection.execute( statement, multiparams, execution_options=execution_options ) 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=? WHERE cookies.cookie_id = ?] [parameters: (-8, 2)] (Background on this error at: https://sqlalche.me/e/20/gkpj) ERROR:root:нужен rollback Traceback (most recent call last): File "/tmp/babel-0NE78C/python-uG2oyE", line 206, in print(session.execute(stmt).all()) ~~~~~~~~~~~~~~~^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2373, in execute return self._execute_internal( ~~~~~~~~~~~~~~~~~~~~~~^ statement, ^^^^^^^^^^ ...<4 lines>... _add_event=_add_event, ^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2261, in _execute_internal conn = self._connection_for_bind(bind) File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2113, in _connection_for_bind return trans._connection_for_bind(engine, execution_options) ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "", line 2, in _connection_for_bind File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/state_changes.py", line 101, in _go self._raise_for_prerequisite_state(fn.__name__, current_state) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 975, in _raise_for_prerequisite_state raise sa_exc.PendingRollbackError( ...<6 lines>... ) sqlalchemy.exc.PendingRollbackError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: (sqlite3.IntegrityError) CHECK constraint failed: quantity_positive [SQL: UPDATE cookies SET quantity=? WHERE cookies.cookie_id = ?] [parameters: (-8, 2)] (Background on this error at: https://sqlalche.me/e/20/gkpj) (Background on this error at: https://sqlalche.me/e/20/7s2a) [('chocolate chip', 3), ('dark chocolate chip', 1)] ERROR:root:CHECK constraint failed: quantity_positive [('chocolate chip', 3), ('dark chocolate chip', 1)] #+end_example