Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

SQLAlchemy Primer

831 views

Published on

Extra slide content of SQLAlchemy introduction talk for Kobe Pythonista meeting #13, 2017/09/15.

Published in: Engineering
  • Be the first to comment

SQLAlchemy Primer

  1. 1. SQLAlchemy Primer (extra content) for Kobe Python Meetup #13 2017/09/15 Kobe Japan
  2. 2. Yasushi Masuda PhD ( @whosaysni ) Tech team, Core IT grp. IT Dept. MonotaRO Co., LTD. Pythonista since 2001 (2.0~) • elaphe (barcode library) • oikami.py (老神.py) • PyCon JP founder Japanese Translation works
  3. 3. Agenda Myths Core concepts in SQLAlchemy
 
 Engine basics (+hands-on) ORM primer (+hans-on)
  4. 4. References Online Document:
 http://docs.sqlalchemy.org/ en/rel_1_1/
 
 (Old) Japanese translation:
 http://omake.accense.com/ static/doc-ja/sqlalchemy/
  5. 5. Preparation sakila DB on SQLite http://bit.ly/2fdeeft https://github.com/jOOQ/jOOQ/jOOQ-examples/Sakila/sqlite-sakila-db/sqlite-sakila.sq Sakila • Demonstration DB for MySQL • Models a rental video shop • BSD License Schema described at: https://dev.mysql.com/doc/sakila/en/sakila-structure-tables.html
  6. 6. Myths [WRONG!] It's just an ORM library
 Not limited to. SQLAlchemy is a DB manipulation framework. [WRONG!] SA is built on ORM
 NO. Connection management and SQL expression framework are its core.
 ORM is built on them.
 
 [WRONG!] SA cannot handle raw SQL
 Table definition is not required. Even textual SQL is available. [WRONG!] SA automatically escapes value for you
 SQLAlchemy relies value escaping on DB-API, while it escapes schema, table, column names. SQLAlchemy generates parameterized query that helps DB-API level escaping. [WRONG!] Only Python adept can handle SA
 SQLAlchemy is EASY. You may need SQL and RDBMS experience. Let's see! [AGREE] Documentation is somewhat difficult to understand
 Agreed.
  7. 7. Core concepts
  8. 8. http://docs.sqlalchemy.org/en/latest/index.html
  9. 9. http://docs.sqlalchemy.org/en/latest/index.html SQLAlchemy Core Engine (connection) Schema definition
 SQL Expression SQLAlchemy ORM Mapper Declarative Mapper Session Dialect DB Backend-specific functionalities
  10. 10. Engine manages DB connection(s) SQL Expression
 describes SQL statement in Python Mapping
 reflects DB record with Python object Dialect DB Backend specific functionalities
  11. 11. Database Your program SQL construction Query execution Type
 conversion Parameter binding Driver Setup Connection management Result data structure Value escaping Schema name Type conversion Dialect- specific Query Construction Query Execution Result Schema object Object-relational mapping High-level Interface DB Session
  12. 12. Engine
  13. 13. DB API DB ServerProgram Issues around DB-API Which DB-API to use How to initialize the DB-API How to generate valid query for it How to handle cursor on the DB-API How to execute query on the DB-API How to retrieve result from DB-API How to reserve/reuse connection
  14. 14. DB API DB ServerProgram Engine resolves issues Select DB-API from DSN URL Initialize DB-API for you Accept string and SQL expression Manage cursor for you Unify execution/transaction API Handle result via ResultProxy Pool connection automatically Engine
  15. 15. Engine DB API DB ServerProgram >>> from sqlalchemy import create_engine >>>
  16. 16. Engine DB API DB ServerProgram >>> from sqlalchemy import create_engine >>> e = create_engine('sqlite://') # SQLite memory engine >>>
  17. 17. Engine DB API DB ServerProgram # Use URL for specifying database >>> e = create_engine('sqlite://') # SQLite memory engine >>> e = create_engine('sqlite:///path_to_db.sqlite3') # sqlite3 >>> e = create_engine('mysql://scott:tiger@dbserv/dbname') # mysql >>> e = create_engine('mssql://bill:gates@dbserv/dbname') # mssql >>>
  18. 18. Engine DB API DB ServerProgram # Be careful with number of slashes >>> e = create_engine('sqlite:///sqlite-sakila.sq') >>> e = create_engine('sqlite:////<absolute_path>/sqlite-sakila.sq')
  19. 19. Engine DB API DB ServerProgram >>> e = create_engine('sqlite:///sqlite-sakila.sq') >>> e Engine(sqlite:///sakila-data.sq) # execute returns ResultProxy >>> q = 'select title from film limit 5') >>> res = e.execute(q) >>> res <sqlalchemy.engine.result.ResultProxy object at 0x10da96990> >>>
  20. 20. Engine DB API DB ServerProgram >>> e = create_engine('sqlite:///sqlite-sakila.sq') >>> e Engine(sqlite:///sakila-data.sq) # execute returns ResultProxy >>> q = 'select title from film limit 5') >>> res = e.execute(q) >>> res <sqlalchemy.engine.result.ResultProxy object at 0x10da96990 >>> for row in res: # ResultProxy can be iterated/namedtuple access ... print(row.title) ... ACADEMY DINOSAUR ACE GOLDFINGER ADAPTATION HOLES AFFAIR PREJUDICE AFRICAN EGG >>>
  21. 21. Engine DB API DB ServerProgram >>> q = 'select film_id, title from film limit 10' >>> rows = list(e.execute(q)) >>> rows[2][1] # each row is accessible like as tuple 'ADAPTATION HOLES' >>> rows[4]['title'] # row can be accessible like as dictionary 'AFRICAN EGG' >>> res = e.execute(q) >>> for fid, title in res: # can be expanded as normal tuple ... print((fid, title)) ... (1, 'ACADEMY DINOSAUR') (2, 'ACE GOLDFINGER') (3, 'ADAPTATION HOLES') (4, 'AFFAIR PREJUDICE') (5, 'AFRICAN EGG') >>> rows = list(e.execute(q)) >>>
  22. 22. transaction >>> t = e.begin()
 >>> t.transaction
 <sqlalchemy...RootTransaction object at ...>
 >>> t.transaction.commit() # with statement handles transaction smart
 >>> with e.begin():
 e.execute(...)
 >>> # (transaction committed automatically)

  23. 23. HANDS ON: Engine basics Connect to sqlite- sakila.sq database List actors in film "DINOSAUR SECRETARY"
  24. 24. HANDS ON: Engine basics >>> e = create_engine('sqlite:///sqlite-sakila.sq')
 >>> q = '''select a.first_name, a.last_name
 ... from film as f 
 ... inner join film_actor as fa 
 ... on f.film_id=fa.film_id 
 ... inner join actor as a
 ... on fa.actor_id=a.actor_id 
 ... where f.title = "DINOSAUR SECRETARY"'''
 >>> for first_name, last_name in e.execute(q):
 ... print('{} {}'.format(first_name, last_name))
 ...
 LUCILLE TRACY
 BURT DUKAKIS
 JAYNE NEESON
 RUSSELL BACALL
 PENELOPE MONROE
 MINNIE KILMER
  25. 25. SQL expression
  26. 26. Remember: SQL is a language SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ] [ * | expression [ [ AS ] output_name ] [, ...] ] [ FROM from_item [, ...] ] [ WHERE condition ] [ GROUP BY grouping_element [, ...] ] [ ORDER BY expression
 [ ASC | DESC | USING operator ]
 [ NULLS { FIRST | LAST } ] [, ...] ] [ LIMIT { count | ALL } ]
  27. 27. Remember: SQL is a language SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ] [ * | expression [ [ AS ] output_name ] [, ...] ] [ FROM from_item [, ...] ] [ WHERE condition ] [ GROUP BY grouping_element [, ...] ] [ ORDER BY expression
 [ ASC | DESC | USING operator ]
 [ NULLS { FIRST | LAST } ] [, ...] ] [ LIMIT { count | ALL } ] STATEMENT
  28. 28. Remember: SQL is a language SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ] [ * | expression [ [ AS ] output_name ] [, ...] ] [ FROM from_item [, ...] ] [ WHERE condition ] [ GROUP BY grouping_element [, ...] ] [ ORDER BY expression
 [ ASC | DESC | USING operator ]
 [ NULLS { FIRST | LAST } ] [, ...] ] [ LIMIT { count | ALL } ] CLAUSE CLAUSE CLAUSE CLAUSE CLAUSE CLAUSE
  29. 29. Remember: SQL is a language SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ] [ * | expression [ [ AS ] output_name ] [, ...] ] [ FROM from_item [, ...] ] [ WHERE condition ] [ GROUP BY grouping_element [, ...] ] [ ORDER BY expression
 [ ASC | DESC | USING operator ]
 [ NULLS { FIRST | LAST } ] [, ...] ] [ LIMIT { count | ALL } ] parameter parameter expression expression expression expression expression expression
  30. 30. Remember: SQL is a Language SELECT FROM selectables selectable join WHERE selectable selectable GROUP BY ORDER BY expression expression condition expression expressions expression expression SELECT statement
  31. 31. SELECT A.name, B.title, C.title FROM artists as A INNER JOIN album as B ON A.id = B.artist_id INNER JOIN music as C ON B.id = C.album WHERE A.name LIKE '%Astor%' AND A.year BETWEEN 1970 AND 2010 AND C.title LIKE '%Tango%' From SQL to Python
  32. 32. SELECT <column>, <column>, ... FROM <selectable> INNER JOIN <selectable> ON <condition> INNER JOIN <selectable> ON <condition> ... WHERE <condition> AND <condition> AND <condition> GROUP BY ... LIMIT ... Clauses
  33. 33. SELECT <[<column>, <column>, <column>]> FROM <join (<selectable>, <selectable>, ...)> WHERE <and (<condition>, <condition>, ...)> Statement and subjects
  34. 34. SELECT <expressions> FROM <selectable> WHERE <conditions> ... simplifed
  35. 35. <select statement> ..., finally
  36. 36. engine.execute(<select statement>) If query is "an object"... Query object
  37. 37. >>> query = select(...) >>> engine.execute(query) ... it can be "execute()-able" Engine "compiles" query into string and execute it (according to dialect)
  38. 38. query = select( <expression>, from_obj=<selectables>, whereclause=<conditions>, ) clauses as parameters
  39. 39. columns = [col1, col2, ...] fromobj = join(tbl1, tbl2, ...) where = and_(expr1, expr2, ...) query_expr = select( columns, from_obj=fromobj, whereclause=where) query with SQL expression
  40. 40. >>> from sqlalchemy.sql import select, text >>> q = select([text('*')]) building sql statement with basic sql expression
  41. 41. >>> from sqlalchemy.sql import select, text >>> q = select([text('*')]) >>> q <sqlalchemy.....Select at ...; Select object> >>> str(q) 'SELECT *' >>> q = select([text('*')], ... from_obj=text('foo'), ... whereclause=text('id=3')) >>> str(q) 'SELECT * nFROM foo nWHERE id=3' building sql statement with basic sql expression
  42. 42. >>> from sqlalchemy.sql import select, text >>> q = select([text('*')]) >>> q <sqlalchemy.....Select at ...; Select object> >>> str(q) 'SELECT *' >>> q = select([text('*')], ... from_obj=text('foo'), ... whereclause=text('id=3')) >>> str(q) 'SELECT * nFROM foo nWHERE id=3' building sql statement with basic sql expression
  43. 43. elements: table and column Schema Table Table Table ... Column Column Column ... Column Column Column ... Column Column Column ... Schema Table Table Table Column Column Column Column Column Column ...
  44. 44. elements: table and column >>> from sqlalchemy.sql import column, table >>> from sqlalchemy import INTEGER
  45. 45. elements: table and column >>> from sqlalchemy.sql import column, table >>> from sqlalchemy import INTEGER >>> c = column('name') # simplest >>> c = column('name', type_=INTEGER)
  46. 46. elements: table and column >>> from sqlalchemy.sql import column, table >>> from sqlalchemy import INTEGER >>> c = column('name') # simplest >>> c = column('name', type_=INTEGER) >>> c <sqlalchemy....ColumnClause at ...; name> >>> str(c) 'name'
  47. 47. elements: table and column >>> from sqlalchemy.sql import column, table >>> from sqlalchemy import INTEGER >>> c = column('name') # simplest >>> c = column('name', type_=INTEGER) >>> c <sqlalchemy....ColumnClause at ...; name> >>> str(c) 'name' >>> c.table # None >>> t1 = table('artist') >>> c.table = t1 >>> str(c) 'artist.name'
  48. 48. >>> t = table('tbl1', column('col1'), ...) defining table with columns Table name List of columns
  49. 49. >>> t = table('tbl1', column('col1'), ...) >>> t.c.col1 <sqlalchemy.....ColumnClause at ...; col1> defining table with columns Table name List of columns
  50. 50. >>> t = table('tbl1', column('col1'), ...) >>> t.c.col1 <sqlalchemy.....ColumnClause at ...; col1> >>> t.schema = 'db1' >>> str(t.c.col1) 'db1.tbl1.col1' defining table with columns Table name List of columns
  51. 51. >>> t = table('tbl1', column('col1'), column('col2')) select() with table element
  52. 52. >>> t = table('tbl1', column('col1'), column('col2')) >>> print(select([t])) SELECT tbl1.col1, tbl1.col2 FROM tbl1 select() with table element
  53. 53. >>> t = table('tbl1', column('col1'), column('col2')) >>> print(select([t])) SELECT tbl1.col1, tbl1.col2 FROM tbl1 # column labeling >>> print(select([t.c.col1.label('col_alias1')]) SELECT tbl1.col1 as "col_alias1" FROM tbl1 select() with table element
  54. 54. >>> t = table('tbl1', column('col1'), column('col2')) >>> print(select([t])) SELECT tbl1.col1, tbl1.col2 FROM tbl1 # column labeling >>> print(select([t.c.col1.label('col_alias1')]) SELECT tbl1.col1 as "col_alias1" FROM tbl1 # table alias >>> t_A, t_B = alias(t, 'A'), alias(t, 'B') >>> print(select([t_A.c.col1, t_B.c.col2])) SELECT "A".col1, "B".col2 FROM tbl1 AS "A", tbl1 AS "B" select() with table element
  55. 55. how to work with where tbl1.col1 = 42 conditionals
  56. 56. how to work with where tbl1.col1 = 42 conditionals conditional expression (compare operation)
  57. 57. >>> cond = text('last_name LIKE %KOV') >>> str(cond) 'last_name LIKE %KOV' conditional by text()
  58. 58. >>> cond = column('last_name').like('%KOV') >>> cond <sqlalchemy....BinaryExpression object at ...> conditional by like() method
  59. 59. >>> cond = column('last_name').like('%KOV') >>> cond <sqlalchemy....BinaryExpression object at ...> >>> str(cond) 'last_name LIKE :last_name_1' conditional by like() method placeholder for right value of LIKE
  60. 60. >>> cond = column('last_name').like('%KOV') >>> cond <sqlalchemy....BinaryExpression object at ...> >>> str(cond) 'last_name LIKE :last_name_1 >>> cond.right BindParameter('%(4339977744 last_name)s', '%KOV', type_=String()) conditional by like() method
  61. 61. >>> column('first_name') = 'DAVID' File "<stdin>", line 1 SyntaxError: can't assign to function call conditional by operation
  62. 62. >>> column('first_name') = 'DAVID' File "<stdin>", line 1 SyntaxError: can't assign to function call >>> column('first_name') == 'DAVID' <sqlalchemy...BinaryExpression object at ...> conditional by operation
  63. 63. >>> column('first_name') = 'DAVID' File "<stdin>", line 1 SyntaxError: can't assign to function call >>> column('first_name') == 'DAVID' <sqlalchemy...BinaryExpression object at ...> >>> cond = column('first_name') == 'DAVID' >>> str(cond) >>> 'first_name = :first_name_1' >>> cond.right >>> BindParameter('%(...)s', 'DAVID', type_=...) conditional by operation
  64. 64. >>> column('first_name') = 'DAVID' File "<stdin>", line 1 SyntaxError: can't assign to function call >>> column('first_name') == 'DAVID' <sqlalchemy...BinaryExpression object at ...> >>> cond = column('first_name') == 'DAVID' >>> str(cond) >>> 'first_name = :first_name_1' >>> cond.right >>> BindParameter('%(...)s', 'DAVID', type_=...) >>> str(column('last_name') == None) >>> 'last_name is NULL' conditional by operation
  65. 65. >>> from sqlalchemy.sql import select, table, column >>> actor_tbl = table('actor', column('actor_id'),
 ... column('first_name'), column('last_name')) conditionals in select
  66. 66. >>> from sqlalchemy.sql import select, table, column >>> actor_tbl = table('actor', column('actor_id'),
 ... column('first_name'), column('last_name')) >>> query = select([actor_tbl], ... whereclause=(actor_tbl.c.last_name=='TRACY')) conditionals in select
  67. 67. >>> from sqlalchemy.sql import select, table, column >>> actor_tbl = table('actor', column('actor_id'),
 ... column('first_name'), column('last_name')) >>> query = select([actor_tbl], ... whereclause=(actor_tbl.c.last_name=='TRACY')) >>> query <sqlalchemy.....Select at ...; Select object> >>> print(query) SELECT actor.first_name, actor.last_name FROM actor WHERE actor.last_name = :last_name_1 >>> query.compile().params {'last_name_1': 'TRACY'} conditionals in select
  68. 68. >>> from sqlalchemy.sql import select, table, column >>> actor_tbl = table('actor', column('actor_id'),
 ... column('first_name'), column('last_name')) >>> query = select([actor_tbl], ... whereclause=(actor_tbl.c.last_name=='TRACY')) >>> query <sqlalchemy.....Select at ...; Select object> >>> print(query) SELECT actor.actor_id, actor.first_name, actor.last_name FROM actor WHERE actor.last_name = :last_name_1 >>> query.compile().params {'last_name_1': 'TRACY'} >>> list(e.execute(query)) [(20, 'LUCILLE', 'TRACY'), (117, 'RENEE', 'TRACY')] conditionals in select
  69. 69. select(...) SELECT ... select(...).select_from(...) SELECT ... FROM ... select(...).where(...) SELECT ... WHERE ... select(...).where(...).where(...) SELECT ... WHERE ... AND ... select(...).where(...).order_by(...) SELECT ... WHERE ... ORDER BY ... generative method
  70. 70. actor_tbl.select() SELECT ... FROM actor actor_tbl.select(actor_tbl.c.first_name='BEN') SELECT ... FROM actor WHERE first_name=... generative method
  71. 71. HANDS ON: SQL expression • define actor table with table()/column() • build query with sql expression to search
 actor having initial A.H. 
 (result should include actor_id)
  72. 72. Schema definition
  73. 73. how to CREATE table schema definition
  74. 74. how to CREATE table how to define column detail how to define constraints schema definition
  75. 75. >>> from sqlalchemy import Column, MetaData, Table >>> user_table = Table( ... 'user', MetaData(), ... Column('id', INTEGER, primary_key=True),
 ... Column('first_name', VARCHAR(45)),
 ... Column('last_name', VARCHAR(45))) >>> defining table
  76. 76. >>> from sqlalchemy import Column, MetaData, Table >>> user_table = Table( ... 'user', MetaData(), ... Column('id', INTEGER, primary_key=True),
 ... Column('first_name', VARCHAR(45)),
 ... Column('last_name', VARCHAR(45))) >>> user_table Table('user', MetaData(bind=None), Column...) defining table
  77. 77. >>> from sqlalchemy import Column, MetaData, Table >>> user_table = Table( ... 'user', MetaData(), ... Column('id', INTEGER, primary_key=True),
 ... Column('first_name', VARCHAR(45)),
 ... Column('last_name', VARCHAR(45))) >>> user_table Table('user', MetaData(bind=None), Column...) # CREATE TABLE by crete() >>> user_table.create(bind=e) # DROP TABLE with drop() >>> user_table.drop(bind=e) defining table
  78. 78. Table/Column vs table/column Visitable (base type) ClauseElement Selectable FromClause [ table() ] ColumnElement ColumnClause [ column() ] Column Table SchemaItem
  79. 79. Table/Column vs table/column Visitable (base type) ClauseElement Selectable FromClause [ table() ] ColumnElement ColumnClause [ column() ] Column Table SchemaItem Supprorts same operation
  80. 80. HANDS ON: Schema definition • define "user" table with Table()/Column() • create table with .create() • insert records • drop table with .drop() name type options id INTEGER primary_key username VARCHAR (64) nullable=False email VARCHAR
 (128) nullable=False
  81. 81. ORM basics
  82. 82. Object-Relational Mapping • Map DB records to objects • ActiveRecord pattern • 1 table ~> 1 class, 1 record ~> 1 object • FK reference -> reference to other object • column -> property/attribute of object
  83. 83. ORM in SQLAlchemy • mapper maps a table with Python class
 mapped class will have instrumented attribute • Session manipulates DB for you to retrieve / save mapped objects
  84. 84. mapping Table Column foo Column bar Column baz ... entity class "mapped" entity class instrumented foo instrumented bar instrumented baz ... method qux method quux method qux method quux ... mapper
  85. 85. classic mapping [new] declarative mapping mapping patterns
  86. 86. >>> from sqlalchemy.orm import mapper >>> actor_tbl = Table(...) >>> class Actor(object): pass >>> mapper(Actor, actor_tbl) >>> dir(Actor) ['__class__', ... '_sa_class_manager', 'actor_id', 'first_name', 'last_name'] >>> Actor.actor_id <...InstrumentedAttribute object at ...> classic mapping
  87. 87. >>> from sqlalchemy.ext.declarative import declarative_base >>> from sqlalchmy import DateTime, Integer, String >>> Base = declarative_base() >>> >>> class Actor(Base): ... __tablename__ = 'actor' ... actor_id = Column(Integer, primary_key=True) ... first_name = Column(String) ... last_name = Column(String) ... last_update = Column(DateTime) declarative mapping
  88. 88. >>> from sqlalchemy.ext.declarative import declarative_base >>> from sqlalchmy import DateTime, Integer, String >>> Base = declarative_base() >>> >>> class Actor(Base): ... __tablename__ = 'actor' ... actor_id = Column(Integer, primary_key=True) ... first_name = Column(String) ... last_name = Column(String) ... last_update = Column(DateTime) >>> dir(Actor) ['__class__', '_decl_class_registry', '_sa_class_manager', 'actor_id', 'first_name', 'last_name', 'last_update', 'metadata'] declarative mapping
  89. 89. ORM in SQLAlchemy • mapper maps a table with Python class
 mapped class will have instrumented attribute • Session manipulates DB for you to retrieve / save mapped objects
  90. 90. session Engine Program query A select A record Aobject B start
 tracking A... (updates) flag A as "dirty" reflect new/dirty changes flush start
 tracking B ... add object B update A insert B commit begin
  91. 91. Using Session >>> from sqlalchemy.orm import sessionmaker >>> Session = sessionmaker(bind=e) >>> session = Session() >>> >>>
  92. 92. Using Session >>> from sqlalchemy.orm import sessionmaker >>> Session = sessionmaker(bind=e) >>> session = Session() >>> query = session.query(Actor) >>> query <sqlalchemy.orm.query.Query object at ...>
  93. 93. Using Session >>> from sqlalchemy.orm import sessionmaker >>> Session = sessionmaker(bind=e) >>> session = Session() >>> query = session.query(Actor) >>> query <sqlalchemy.orm.query.Query object at ...> >>> actor = query.first() >>> actor <...Actor Object at ...>
  94. 94. Query object methods: query filtering >>> q = session.query(Actor) >>> str(q) >>> 'SELECT ... nFROM actor' >>> q1 = q.filter(Actor.first_name=='BEN') >>> str(q1) 'SELECT ... nFROM actornWHERE actor.first_name = ?' >>> q2 = q1.filter_by(last_name='SMITH') >>> str(q2) 'SELECT ... nFROM actornWHERE actor.first_name = ? AND actor.last_name= ?'
  95. 95. Query object methods: fetching record(s) >>> q = session.query(Actor) >>> q.first() <...Actor object at ...> >>> q.all() # WARNING: SELECTs all records <...Actor object at ...>, <...Actor object at ...>, ... >>> q[:3] <...Actor object at ...>, <...Actor object at ...>, ... >>> q.count() 200
  96. 96. Update/Insert in Session >>> session = Session() >>> actor_a = Actor(first_name='KEN',
 ... last_name='WATANABE') >>> session.add(actor_a) >>> session.commit() >>> actor_b = session.query(Actor).get(1) >>> actor_b.last_name='GEORGE' >>> session.commit()
  97. 97. HANDS ON: ORM basics • define Category model with declarative ORM • select any record • add "Nature" category • delete "Nature" category

×