Files
sqlalchemy-alembic/Essential-SQLAlchemy-2nd.org
T
2026-09-26 16:04:33 +05:00

6006 lines
205 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#+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: <link rel="stylesheet" type="text/css" href="solarized-dark.css" />
#+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"
<<A1>>
<<B1>>
<<C1>>
<<D1>>
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:<sqlalchemy.engine.cursor.CursorResult object at 0x7fa8f6747950>
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 <module>
[(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 <module>
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 <module>
ship_it(2)
~~~~~~~^^^
File "<string>", 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 <module>
connection.execute(ins)
~~~~~~~~~~~~~~~~~~^^^^^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1421, in execute
return meth(
self,
distilled_parameters,
execution_options or NO_OPTIONS,
)
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection
return connection._execute_clauseelement(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
self, distilled_params, execution_options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1643, in _execute_clauseelement
ret = self._execute_context(
dialect,
...<8 lines>...
cache_hit=cache_hit,
)
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
return self._exec_single_context(
~~~~~~~~~~~~~~~~~~~~~~~~~^
dialect, context, statement, parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
self._handle_dbapi_exception(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
e, str_statement, effective_parameters, cursor, context
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
~~~~~~~~~~~~~~~~~~~~~~~^
cursor, str_statement, effective_parameters, context
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
cursor.execute(statement, parameters)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: users.username
[SQL: INSERT INTO users (username, email_address, phone, password, created_on, updated_on) VALUES (?, ?, ?, ?, ?, ?)]
[parameters: ('cookiemon', 'damon@cookie.com', '111-111-1111', 'password', '2026-09-18 12:21:58.917037', '2026-09-18 12:21:58.917039')]
(Background on this error at: https://sqlalche.me/e/20/gkpj)
2026-09-18 12:21:58,921 INFO sqlalchemy.engine.Engine INSERT INTO cookies (cookie_name, cookie_recipe_url, cookie_sku, quantity, unit_cost) VALUES (?, ?, ?, ?, ?)
INFO:sqlalchemy.engine.Engine:INSERT INTO cookies (cookie_name, cookie_recipe_url, cookie_sku, quantity, unit_cost) VALUES (?, ?, ?, ?, ?)
2026-09-18 12:21:58,921 INFO sqlalchemy.engine.Engine [generated in 0.00022s] [('chocolate chip', 'http://some.aweso.me/cookie/recipe.html', 'CC01', '12', 0.5), ('dark chocolate chip', 'http://some.aweso.me/cookie/recipe_dark.html', 'CC02', '1', 0.75)]
INFO:sqlalchemy.engine.Engine:[generated in 0.00022s] [('chocolate chip', 'http://some.aweso.me/cookie/recipe.html', 'CC01', '12', 0.5), ('dark chocolate chip', 'http://some.aweso.me/cookie/recipe_dark.html', 'CC02', '1', 0.75)]
2026-09-18 12:21:58,922 INFO sqlalchemy.engine.Engine INSERT INTO orders (order_id, user_id, shipped) VALUES (?, ?, ?)
INFO:sqlalchemy.engine.Engine:INSERT INTO orders (order_id, user_id, shipped) VALUES (?, ?, ?)
2026-09-18 12:21:58,922 INFO sqlalchemy.engine.Engine [generated in 0.00015s] ('1', 1, 0)
INFO:sqlalchemy.engine.Engine:[generated in 0.00015s] ('1', 1, 0)
2026-09-18 12:21:58,922 INFO sqlalchemy.engine.Engine INSERT INTO line_items (order_id, cookie_id, quantity, extended_cost) VALUES (?, ?, ?, ?)
INFO:sqlalchemy.engine.Engine:INSERT INTO line_items (order_id, cookie_id, quantity, extended_cost) VALUES (?, ?, ?, ?)
2026-09-18 12:21:58,922 INFO sqlalchemy.engine.Engine [generated in 0.00014s] (1, 1, 9, 4.5)
INFO:sqlalchemy.engine.Engine:[generated in 0.00014s] (1, 1, 9, 4.5)
2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine INSERT INTO orders (order_id, user_id, shipped) VALUES (?, ?, ?)
INFO:sqlalchemy.engine.Engine:INSERT INTO orders (order_id, user_id, shipped) VALUES (?, ?, ?)
2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine [cached since 0.001143s ago] ('2', 1, 0)
INFO:sqlalchemy.engine.Engine:[cached since 0.001143s ago] ('2', 1, 0)
2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine INSERT INTO line_items (order_id, cookie_id, quantity, extended_cost) VALUES (?, ?, ?, ?)
INFO:sqlalchemy.engine.Engine:INSERT INTO line_items (order_id, cookie_id, quantity, extended_cost) VALUES (?, ?, ?, ?)
2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine [generated in 0.00011s] [(2, 1, 4, 1.5), (2, 2, 1, 4.5)]
INFO:sqlalchemy.engine.Engine:[generated in 0.00011s] [(2, 1, 4, 1.5), (2, 2, 1, 4.5)]
2026-09-18 12:21:58,923 INFO sqlalchemy.engine.Engine COMMIT
INFO:sqlalchemy.engine.Engine:COMMIT
2026-09-18 12:21:58,924 INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO:sqlalchemy.engine.Engine:BEGIN (implicit)
2026-09-18 12:21:58,924 INFO sqlalchemy.engine.Engine SELECT line_items.cookie_id, line_items.quantity
FROM line_items
WHERE line_items.order_id = ?
INFO:sqlalchemy.engine.Engine:SELECT line_items.cookie_id, line_items.quantity
FROM line_items
WHERE line_items.order_id = ?
2026-09-18 12:21:58,924 INFO sqlalchemy.engine.Engine [generated in 0.00010s] (1,)
INFO:sqlalchemy.engine.Engine:[generated in 0.00010s] (1,)
2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ?
INFO:sqlalchemy.engine.Engine:UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ?
2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine [generated in 0.00009s] (9, 1)
INFO:sqlalchemy.engine.Engine:[generated in 0.00009s] (9, 1)
2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine UPDATE orders SET shipped=? WHERE orders.order_id = ?
INFO:sqlalchemy.engine.Engine:UPDATE orders SET shipped=? WHERE orders.order_id = ?
2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine [generated in 0.00012s] (1, 1)
INFO:sqlalchemy.engine.Engine:[generated in 0.00012s] (1, 1)
Shipped order ID: 1
2026-09-18 12:21:58,926 INFO sqlalchemy.engine.Engine COMMIT
INFO:sqlalchemy.engine.Engine:COMMIT
2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO:sqlalchemy.engine.Engine:BEGIN (implicit)
2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine SELECT cookies.cookie_name, cookies.quantity
FROM cookies
INFO:sqlalchemy.engine.Engine:SELECT cookies.cookie_name, cookies.quantity
FROM cookies
2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine [generated in 0.00012s] ()
INFO:sqlalchemy.engine.Engine:[generated in 0.00012s] ()
[('chocolate chip', 3), ('dark chocolate chip', 1)]
2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine ROLLBACK
INFO:sqlalchemy.engine.Engine:ROLLBACK
2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO:sqlalchemy.engine.Engine:BEGIN (implicit)
2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine SELECT line_items.cookie_id, line_items.quantity
FROM line_items
WHERE line_items.order_id = ?
INFO:sqlalchemy.engine.Engine:SELECT line_items.cookie_id, line_items.quantity
FROM line_items
WHERE line_items.order_id = ?
2026-09-18 12:21:58,927 INFO sqlalchemy.engine.Engine [cached since 0.002892s ago] (2,)
INFO:sqlalchemy.engine.Engine:[cached since 0.002892s ago] (2,)
2026-09-18 12:21:58,928 INFO sqlalchemy.engine.Engine UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ?
INFO:sqlalchemy.engine.Engine:UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ?
2026-09-18 12:21:58,928 INFO sqlalchemy.engine.Engine [cached since 0.002047s ago] (4, 1)
INFO:sqlalchemy.engine.Engine:[cached since 0.002047s ago] (4, 1)
ERROR:root:Failed to ship order 2
Traceback (most recent call last):
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
~~~~~~~~~~~~~~~~~~~~~~~^
cursor, str_statement, effective_parameters, context
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
cursor.execute(statement, parameters)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.IntegrityError: CHECK constraint failed: quantity_positive
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/tmp/babel-sg4Ps9/python-ObJFq6", line 224, in ship_it_atomic
conn.execute(u)
~~~~~~~~~~~~^^^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1421, in execute
return meth(
self,
distilled_parameters,
execution_options or NO_OPTIONS,
)
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection
return connection._execute_clauseelement(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
self, distilled_params, execution_options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1643, in _execute_clauseelement
ret = self._execute_context(
dialect,
...<8 lines>...
cache_hit=cache_hit,
)
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
return self._exec_single_context(
~~~~~~~~~~~~~~~~~~~~~~~~~^
dialect, context, statement, parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
self._handle_dbapi_exception(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
e, str_statement, effective_parameters, cursor, context
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
~~~~~~~~~~~~~~~~~~~~~~~^
cursor, str_statement, effective_parameters, context
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
cursor.execute(statement, parameters)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) CHECK constraint failed: quantity_positive
[SQL: UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ?]
[parameters: (4, 1)]
(Background on this error at: https://sqlalche.me/e/20/gkpj)
2026-09-18 12:21:58,930 INFO sqlalchemy.engine.Engine ROLLBACK
INFO:sqlalchemy.engine.Engine:ROLLBACK
ERROR:root:
Traceback (most recent call last):
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
~~~~~~~~~~~~~~~~~~~~~~~^
cursor, str_statement, effective_parameters, context
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
cursor.execute(statement, parameters)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.IntegrityError: CHECK constraint failed: quantity_positive
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/tmp/babel-sg4Ps9/python-ObJFq6", line 241, in <module>
ship_it_atomic(2)
~~~~~~~~~~~~~~^^^
File "/tmp/babel-sg4Ps9/python-ObJFq6", line 224, in ship_it_atomic
conn.execute(u)
~~~~~~~~~~~~^^^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1421, in execute
return meth(
self,
distilled_parameters,
execution_options or NO_OPTIONS,
)
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection
return connection._execute_clauseelement(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
self, distilled_params, execution_options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1643, in _execute_clauseelement
ret = self._execute_context(
dialect,
...<8 lines>...
cache_hit=cache_hit,
)
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
return self._exec_single_context(
~~~~~~~~~~~~~~~~~~~~~~~~~^
dialect, context, statement, parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
self._handle_dbapi_exception(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
e, str_statement, effective_parameters, cursor, context
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
~~~~~~~~~~~~~~~~~~~~~~~^
cursor, str_statement, effective_parameters, context
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/octopus/study/Python/sqlalchemy-study/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
cursor.execute(statement, parameters)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) CHECK constraint failed: quantity_positive
[SQL: UPDATE cookies SET quantity=(cookies.quantity - ?) WHERE cookies.cookie_id = ?]
[parameters: (4, 1)]
(Background on this error at: https://sqlalche.me/e/20/gkpj)
#+end_example
* Chapter 4: Testing
#+BEGIN_SRC python :tangle db.py :shebang "#!/usr/bin/env python3" :results none
from datetime import datetime
from sqlalchemy import (
MetaData,
Table,
Column,
Integer,
Numeric,
String,
DateTime,
ForeignKey,
Boolean,
create_engine,
)
from sqlalchemy.sql import insert
class DataAccessLayer:
connection = None
engine = None
conn_string = None
metadata = MetaData()
cookies = Table(
"cookies",
metadata,
Column("cookie_id", Integer(), primary_key=True),
Column("cookie_name", String(50), index=True),
Column("cookie_recipe_url", String(255)),
Column("cookie_sku", String(55)),
Column("quantity", Integer()),
Column("unit_cost", Numeric(12, 2)),
)
users = Table(
"users",
metadata,
Column("user_id", Integer(), primary_key=True),
Column("customer_number", Integer(), autoincrement=True),
Column("username", String(15), nullable=False, unique=True),
Column("email_address", String(255), nullable=False),
Column("phone", String(20), nullable=False),
Column("password", String(25), nullable=False),
Column("created_on", DateTime(), default=datetime.now),
Column("updated_on", DateTime(), default=datetime.now, onupdate=datetime.now),
)
orders = Table(
"orders",
metadata,
Column("order_id", Integer()),
Column("user_id", ForeignKey("users.user_id")),
Column("shipped", Boolean(), default=False),
)
line_items = Table(
"line_items",
metadata,
Column("line_items_id", Integer(), primary_key=True),
Column("order_id", ForeignKey("orders.order_id")),
Column("cookie_id", ForeignKey("cookies.cookie_id")),
Column("quantity", Integer()),
Column("extended_cost", Numeric(12, 2)),
)
def db_init(self, conn_string):
self.engine = create_engine(conn_string or self.conn_string)
self.metadata.create_all(self.engine)
self.connection = self.engine.connect()
dal = DataAccessLayer()
def prep_db():
ins = dal.cookies.insert()
dal.connection.execute(
ins,
dict(
cookie_name="dark chocolate chip",
cookie_recipe_url="http://some.aweso.me/cookie/recipe_dark.html",
cookie_sku="CC02",
quantity="1",
unit_cost="0.75",
),
)
inventory_list = [
{
"cookie_name": "peanut butter",
"cookie_recipe_url": "http://some.aweso.me/cookie/peanut.html",
"cookie_sku": "PB01",
"quantity": "24",
"unit_cost": "0.25",
},
{
"cookie_name": "oatmeal raisin",
"cookie_recipe_url": "http://some.okay.me/cookie/raisin.html",
"cookie_sku": "EWW01",
"quantity": "100",
"unit_cost": "1.00",
},
]
dal.connection.execute(ins, inventory_list)
customer_list = [
{
"username": "cookiemon",
"email_address": "mon@cookie.com",
"phone": "111-111-1111",
"password": "password",
},
{
"username": "cakeeater",
"email_address": "cakeeater@cake.com",
"phone": "222-222-2222",
"password": "password",
},
{
"username": "pieguy",
"email_address": "guy@pie.com",
"phone": "333-333-3333",
"password": "password",
},
]
ins = dal.users.insert()
dal.connection.execute(ins, customer_list)
ins = insert(dal.orders).values(user_id=1, order_id="wlk001")
dal.connection.execute(ins)
ins = insert(dal.line_items)
order_items = [
{"order_id": "wlk001", "cookie_id": 1, "quantity": 2, "extended_cost": 1.00},
{"order_id": "wlk001", "cookie_id": 3, "quantity": 12, "extended_cost": 3.00},
]
dal.connection.execute(ins, order_items)
ins = insert(dal.orders).values(user_id=2, order_id="ol001")
dal.connection.execute(ins)
ins = insert(dal.line_items)
order_items = [
{"order_id": "ol001", "cookie_id": 1, "quantity": 24, "extended_cost": 12.00},
{"order_id": "ol001", "cookie_id": 4, "quantity": 6, "extended_cost": 6.00},
]
dal.connection.execute(ins, order_items)
#+END_SRC
#+BEGIN_SRC python :tangle app.py :results none :shebang "#!/usr/bin/env python3"
from db import dal
from sqlalchemy.sql import select
def get_orders_by_customer(
cust_name: str, shipped: bool | None = None, details: bool = False
):
columns = [dal.orders.c.order_id, dal.users.c.username, dal.users.c.phone]
joins = dal.users.join(dal.orders)
if details:
columns.extend(
[
dal.cookies.c.cookie_name,
dal.line_items.c.quantity,
dal.line_items.c.extended_cost,
]
)
joins = joins.join(dal.line_items).join(dal.cookies)
cust_orders = select(*columns)
cust_orders = cust_orders.select_from(joins).where(
dal.users.c.username == cust_name
)
if shipped is not None:
cust_orders = cust_orders.where(dal.orders.c.shipped == shipped)
return dal.connection.execute(cust_orders).fetchall()
#+END_SRC
*** UnitTest
#+BEGIN_SRC python :tangle test_app.py
import unittest
from decimal import Decimal
from app import get_orders_by_customer
from db import dal, prep_db
class TestApp(unittest.TestCase):
cookie_orders = [("wlk001", "cookiemon", "111-111-1111")]
cookie_details = [
(
"wlk001",
"cookiemon",
"111-111-1111",
"dark chocolate chip",
2,
Decimal("1.00"),
),
("wlk001", "cookiemon", "111-111-1111", "oatmeal raisin", 12, Decimal("3.00")),
]
@classmethod
def setUpClass(cls):
dal.db_init("sqlite:///:memory:")
prep_db()
def test_orders_by_customer_blank(self):
"""
- значение cust_name пустая строка,
- shipped = None
"""
results = get_orders_by_customer("")
self.assertEqual(results, [])
def test_orders_by_customer_blank_shipped(self):
"""
- значение cust_name пустая строка,
- shipped = True
"""
results = get_orders_by_customer("", True)
self.assertEqual(results, [])
def test_orders_by_customer_blank_notshipped(self):
"""
- значение cust_name пустая строка,
- shipped = False
"""
results = get_orders_by_customer("", False)
self.assertEqual(results, [])
def test_orders_by_customer_blank_details(self):
results = get_orders_by_customer("", details=True)
self.assertEqual(results, [])
def test_orders_by_customer_blank_shipped_details(self):
results = get_orders_by_customer("", True, True)
self.assertEqual(results, [])
def test_orders_by_customer_blank_notshipped_details(self):
results = get_orders_by_customer("", False, True)
self.assertEqual(results, [])
def test_orders_by_customer(self):
expected_results = [("wlk001", "cookiemon", "111-111-1111")]
results = get_orders_by_customer("cookiemon")
self.assertEqual(results, expected_results)
def test_orders_by_customer_shipped_only(self):
results = get_orders_by_customer("cookiemon", True)
self.assertEqual(results, [])
def test_orders_by_customer_unshipped_only(self):
expected_results = [("wlk001", "cookiemon", "111-111-1111")]
results = get_orders_by_customer("cookiemon", False)
self.assertEqual(results, expected_results)
def test_orders_by_customer_with_details(self):
expected_results = [
(
"wlk001",
"cookiemon",
"111-111-1111",
"dark chocolate chip",
2,
Decimal("1.00"),
),
(
"wlk001",
"cookiemon",
"111-111-1111",
"oatmeal raisin",
12,
Decimal("3.00"),
),
]
results = get_orders_by_customer("cookiemon", details=True)
self.assertEqual(results, expected_results)
def test_orders_by_customer_shipped_only_with_details(self):
results = get_orders_by_customer("cookiemon", True, True)
self.assertEqual(results, [])
def test_orders_by_customer_unshipped_only_details(self):
expected_results = [
(
"wlk001",
"cookiemon",
"111-111-1111",
"dark chocolate chip",
2,
Decimal("1.00"),
),
(
"wlk001",
"cookiemon",
"111-111-1111",
"oatmeal raisin",
12,
Decimal("3.00"),
),
]
results = get_orders_by_customer("cookiemon", False, True)
self.assertEqual(results, expected_results)
#+END_SRC
#+BEGIN_SRC python :tangle test_app_mocked.py :results none
import unittest
from decimal import Decimal
from unittest import mock
from app import get_orders_by_customer
from db import dal, prep_db
class TestApp(unittest.TestCase):
cookie_orders = [("wlk001", "cookiemon", "111-111-1111")]
cookie_details = [
(
"wlk001",
"cookiemon",
"111-111-1111",
"dark chocolate chip",
2,
Decimal("1.00"),
),
("wlk001", "cookiemon", "111-111-1111", "oatmeal raisin", 12, Decimal("3.00")),
]
@mock.patch("app.select")
@mock.patch("app.dal.connection")
def test_orders_by_customer_blank(self, mock_conn, mock_select):
mock_select.return_value.select_from.return_value.where.return_value = None
mock_conn.execute.return_value.fetchall.return_value = []
results = get_orders_by_customer("")
self.assertEqual(results, [])
@mock.patch("app.dal.connection")
def test_orders_by_customer_blank_shipped(self, mock_conn):
mock_conn.execute.return_value.fetchall.return_value = []
results = get_orders_by_customer("", True)
self.assertEqual(results, [])
@mock.patch("app.dal.connection")
def test_orders_by_customer(self, mock_conn):
mock_conn.execute.return_value.fetchall.return_value = self.cookie_orders
results = get_orders_by_customer("cookiemon")
self.assertEqual(results, self.cookie_orders)
#+END_SRC
*** pytest
#+BEGIN_SRC python :tangle test_app_pytest.py :results none
from decimal import Decimal
import pytest
from app import get_orders_by_customer
from db import dal, prep_db
@pytest.fixture(autouse=True, scope="class")
def setup_db():
dal.db_init("sqlite:///:memory:")
prep_db()
yield
# здесь можно закрыть соединение, если нужно:
# dal.connection.close()
def test_orders_by_customer_blank():
"""
- значение cust_name пустая строка,
- shipped = None
"""
results = get_orders_by_customer("")
assert results == []
def test_orders_by_customer_blank_shipped():
"""
- значение cust_name пустая строка,
- shipped = True
"""
results = get_orders_by_customer("", True)
assert results == []
def test_orders_by_customer_blank_notshipped():
"""
- значение cust_name пустая строка,
- shipped = False
"""
results = get_orders_by_customer("", False)
assert results == []
def test_orders_by_customer_blank_details():
results = get_orders_by_customer("", details=True)
assert results == []
def test_orders_by_customer_blank_shipped_details():
results = get_orders_by_customer("", True, True)
assert results == []
def test_orders_by_customer_blank_notshipped_details():
results = get_orders_by_customer("", False, True)
assert results == []
def test_orders_by_customer():
expected_results = [("wlk001", "cookiemon", "111-111-1111")]
results = get_orders_by_customer("cookiemon")
assert results == expected_results
def test_orders_by_customer_shipped_only():
results = get_orders_by_customer("cookiemon", True)
assert results == []
def test_orders_by_customer_unshipped_only():
expected_results = [("wlk001", "cookiemon", "111-111-1111")]
results = get_orders_by_customer("cookiemon", False)
assert results == expected_results
def test_orders_by_customer_with_details():
expected_results = [
(
"wlk001",
"cookiemon",
"111-111-1111",
"dark chocolate chip",
2,
Decimal("1.00"),
),
(
"wlk001",
"cookiemon",
"111-111-1111",
"oatmeal raisin",
12,
Decimal("3.00"),
),
]
results = get_orders_by_customer("cookiemon", details=True)
assert results == expected_results
def test_orders_by_customer_shipped_only_with_details():
results = get_orders_by_customer("cookiemon", True, True)
assert results == []
def test_orders_by_customer_unshipped_only_details():
expected_results = [
(
"wlk001",
"cookiemon",
"111-111-1111",
"dark chocolate chip",
2,
Decimal("1.00"),
),
(
"wlk001",
"cookiemon",
"111-111-1111",
"oatmeal raisin",
12,
Decimal("3.00"),
),
]
results = get_orders_by_customer("cookiemon", False, True)
assert results == expected_results
#+END_SRC
#+BEGIN_SRC python :tangle test_app_mocked_pytest.py :results none
from decimal import Decimal
from unittest import mock
import pytest
from app import get_orders_by_customer
from db import dal, prep_db
# --- общие тестовые данные ---
@pytest.fixture
def cookie_orders():
return [("wlk001", "cookiemon", "111-111-1111")]
@pytest.fixture
def cookie_details():
return [
(
"wlk001",
"cookiemon",
"111-111-1111",
"dark chocolate chip",
2,
Decimal("1.00"),
),
("wlk001", "cookiemon", "111-111-1111", "oatmeal raisin", 12, Decimal("3.00")),
]
# --- тесты ---
@mock.patch("app.select")
@mock.patch("app.dal.connection")
def test_orders_by_customer_blank(mock_conn, mock_select):
mock_select.return_value.select_from.return_value.where.return_value = None
mock_conn.execute.return_value.fetchall.return_value = []
results = get_orders_by_customer("")
assert results == []
@mock.patch("app.dal.connection")
def test_orders_by_customer_blank_shipped(mock_conn):
mock_conn.execute.return_value.fetchall.return_value = []
results = get_orders_by_customer("", True)
assert results == []
@mock.patch("app.dal.connection")
def test_orders_by_customer(mock_conn, cookie_orders):
mock_conn.execute.return_value.fetchall.return_value = cookie_orders
results = get_orders_by_customer("cookiemon")
assert results == cookie_orders
#+END_SRC
* 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=<Album>, primary_key=True, nullable=False), Column('Title', NVARCHAR(length=160), table=<Album>, nullable=False), Column('ArtistId', INTEGER(), ForeignKey('Artist.ArtistId'), table=<Album>, 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\(<function datetime\.now at 0x[0-9a-f]+>\)",
"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\(<function datetime\.now at 0x[0-9a-f]+>\)",
"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
<<MyBase>>
<<User>>
<<Order>>
<<LineItem>>
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\(<function datetime\.now at 0x[0-9a-f]+>\)",
"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()
<<MyBase>>
<<User>>
<<Order>>
<<LineItem>>
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)
<<inspect_state>>
#+END_SRC
#+RESULTS[0378d1b2c44b7e1c975841f7ed5c2996a7549d8f]:
| transient | False |
| pending | True |
| persistent | False |
| detached | False |
#+BEGIN_SRC python :results table
session.commit()
<<inspect_state>>
#+END_SRC
#+RESULTS[7ee412ff6ac55f300185fc0b8c2253bba4975e13]:
| transient | False |
| pending | False |
| persistent | True |
| detached | False |
#+BEGIN_SRC python :results table
session.expunge(cc_cookie)
<<inspect_state>>
#+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 <module>
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 <module>
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 <module>
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 <Order at 0x7f47bd840c20> 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 <module>
ship_it(2)
~~~~~~~^^^
File "<string>", 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 "<string>", 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 "<string>", 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 <module>
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 "<string>", 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 <module>
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 "<string>", 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 "<string>", 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 <module>
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 "<string>", 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
* Chapter 10: Reflection with SQLAlchemy ORM and Automap
** SQLAlchemy v1
:PROPERTIES:
:header-args:python: :session *ch10-v1* :tangle ch10-v1.py :cache yes :noweb yes
:END:
#+BEGIN_SRC python :results table :shebang "#!/usr/bin/env python3"
from sqlalchemy.ext.automap import automap_base
Base = automap_base()
from sqlalchemy import create_engine
engine = create_engine("sqlite:///Chinook_Sqlite.sqlite")
Base.prepare(engine, reflect=True)
[(k,) for k in Base.classes.keys()]
#+END_SRC
#+RESULTS[32bdca5524458c540535c004a2d593ed04a87d7b]:
| Playlist |
| Track |
| Customer |
| InvoiceLine |
| Artist |
| Invoice |
| MediaType |
| Genre |
| Employee |
| Album |
#+BEGIN_SRC python :results table
from sqlalchemy.orm import Session
Artist = Base.classes.Artist
Album = Base.classes.Album
session = Session(engine)
[(artist.ArtistId, artist.Name) for artist in session.query(Artist).limit(10)]
#+END_SRC
#+RESULTS[983002791b292f46a0452544998df191dec7c602]:
| 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 |
*** Reflected Relationships
#+BEGIN_SRC python :results table
artist = session.query(Artist).first()
[(artist.Name, album.Title)for album in artist.album_collection]
#+END_SRC
#+RESULTS[9d2f8b99a53eaa7596304d49c1c8cfe470d9a8c8]:
| AC/DC | For Those About To Rock We Salute You |
| AC/DC | Let There Be Rock |
** SQLAlchemy v2
:PROPERTIES:
:header-args:python: :session *ch10-v2* :tangle ch10-v2.py :cache yes :noweb yes
:END:
#+BEGIN_SRC python :shebang "#!/usr/bin/env python3" :results none
from sqlalchemy import create_engine, select
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
# 1. Automap база
Base = automap_base()
# 2. Движок и рефлексия (2.0 стиль: autoload_with)
engine = create_engine("sqlite:///Chinook_Sqlite.sqlite")
Base.prepare(autoload_with=engine)
# 3. Список имён классов
class_names = list(Base.classes.keys())
# 4. Работа с сессией через контекстный менеджер (рекомендуется)
with Session(engine) as session:
Artist = Base.classes.Artist
Album = Base.classes.Album
# 5. Выборка с limit — scalars() возвращает ORM-объекты напрямую
artists = session.scalars(select(Artist).limit(10)).all()
artist_data = [(a.ArtistId, a.Name) for a in artists]
# 6. Первый артист и его альбомы (ленивая загрузка через relationship)
artist = session.scalars(select(Artist)).first()
if artist:
albums_data = [(artist.Name, album.Title) for album in artist.album_collection]
#+END_SRC