Slug: sqlalchemy-intro Title: SQLAlchemy is no sorcery ! Category: Je tisse ma toile Tags: développement, SQLAlchemy, Python, web, un peu compliqué quand même Summary: A first contact with SQLAlchemy Created: 2018/08/10 08:00 Image: /images/sqlalchemy_logo.png Lang: en Status: published ___ ![Logo SQLAlchemy][sqlalchemy-logo] After several months using Django's ORM, the time has come to fiddle with a more powerful toy : Python ORM's top-level library, SQLAlchemy. ## Init Here's a topic that seemed like a huge deal skimming through the documentation, but that is not all that difficult once the steps are well understood. I shamelessly take inspiration from [that excellent article explaining SQLAlchemy to Django users][sqlalchemy-django] and the [official tutorial][sqlalchemy-tutorial]. SQLAlchemy is kind of the Swiss army knife of ORMs, but much of its power is hidden and will be directly summoned only during init. First step is establishing a connection with the database : :::python from sqlalchemy import create_engine engine = create_engine('sqlite:///./db.sqlite3', echo=True) From now on, abstraction from the kind of database is dealt with, and it's not even a concern anymore. One might need direct interactions with that *Engine* object in corner cases, but that's usually not required when using the ORM. Second step is describing to SQLAlchemy the tables that needs to be created and the associated Python classes (which will allow us to interact with the database without a single line of SQL limbo). In modern versions of SQLAlchemy, that's a single-step operation done using a une *declarative base class* provided by the library, and that as its name implies will be used to describe all the application models. :::python from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() Once that base is available, one can derive all user-defined models from it, and SQLAlchemy's magic takes place in background. This is quite close from Django in principle, but several declarations will need to be explicit (the table name, primary key, ...) :::python from sqlalchemy import Column, Integer, String class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String) fullname = Column(String) All the information provided there is aggregated into the *Base* whose "metadata" field can now be used to assemble the whole puzzle (database connection and schema declaration). :::python Base.metadata.create_all(engine) This simple expression will simultaneously create the database, tables (if not created yet), and allow use of the ORM. Inheriting from Base already allowed to create instances of user defined classes : :::ipython3 In [1]: user = User(name="way", fullname="theenglishway") Out [1]: ... but those instances could not be directly sent into the database yet (Django's ORM would have already allowed you to call `user.save()`). You will need another kind of object for that, which is at the heart of SQLAlchemy, the *session* ; the session basically makes the connection to the database, and any transaction should be done using a session. Those session will be created from a *factory* provided by the library, to which such arguments as the *engine* we encountered in the first step should be provided. :::python from sqlalchemy.orm import sessionmaker Session = sessionmaker(bind=engine) This class should be kept easily accessible, for we'll need it every time a connection should be open, which is done simply by `session = Session()`. Everything is now setup for talking with the database, creating entries, making queries, ... The init phase is a bit more verbose than in Django but does not seem as complex once the individual role of each element is well understood. Here's what the whole init file looks like : :::python from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String Base = declarative_base() # This should be moved to another file like 'models.py' or anything # convenient in the file tree we defined class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String) fullname = Column(String) def __repr__(self): return "".format(self.name, self.fullname) engine = create_engine('sqlite:///./sqlite3.db', echo=True) Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) ## Basic use We now have a pretty database with empty tables ; we just have to use it. Unlike Django that did lots of mysterious stuff in you back (for better and for worse), SQLAlchemy provides us with plenty of powerful choices. The most fundamental notion is that of the **_session_**. Tons of articles and talks go into great lengths to explain how this concept is much more powerful than the one provided by more basic ORMs (such as Django's). As far as I understand, and at the cost of a slightly more complex use, it allows : * limiting the number of requests that are effectively sent to the database * rolling-back an entire transaction if needed * ensure consistency within a given transaction, well before the first request is sent An example is worth a thousand words : :::ipython3 # A first session is created In [1]: session = Session() # ... in which a user is added In [2]: session.add(User(name="way", fullname="theenglishway")) # A query within that session returns that user In [3]: session.query(User).all() Out[3]: [] # Another session is open In [4]: session2 = Session() # The new user is not visible yet In [5]: session2.query(User).all() Out[5]: [] # But as soon as the first session is commit ... In [6]: session.commit() # The user appears in the second session ! In [7]: session2.query(User).all() Out[7]: [] ## The special case of web frameworks It is worth noting a little subtlety about sessions when they are used in the environment of a web framework that handles request in a given thread : one should take care to use [**_scoped sessions_**][sqlalchemy-sessions] in that case. As a general rule, SQLAlchemy is usually even more powerful and subtle than you might imagine, so browsing through the [incredibly complete documentation][sqlalchemy-doc] is a must-read before anything ... and, of course, future articles of mine that will touch on that subject ! [sqlalchemy-django]: http://lucumr.pocoo.org/2011/7/19/sqlachemy-and-you/ [sqlalchemy-tutorial]: https://docs.sqlalchemy.org/en/latest/orm/tutorial.html [sqlalchemy-logo]: /images/sqlalchemy_logo.png [sqlalchemy-sessions]: http://docs.sqlalchemy.org/en/latest/orm/contextual.html#using-thread-local-scope-with-web-applications [sqlqlchemy-doc]: https://docs.sqlalchemy.org/en/