.. _migrating_to_v2.0:
Migrating to v2.0
=================
The sqlservice ``v2`` release represents a major design change with many breaking changes. It has been overhauled to implement the patterns that are recommended by SQLAlchemy's 2.0 Core and ORM usage guidelines. Migrating from sqlservice ``v1`` to ``v2`` will require many changes and possibly some new design pattern implementations. This guide will help with the transition to ``2.0``.
.. seealso::
Learn more about the new 2.0 style from the `SQLAlchemy 1.4 / 2.0 Tutorial `_.
New Database Class Replaces SQLClient
-------------------------------------
**breaking change**
.. code-block:: python
# sqlservice 1.x
from sqlservice import SQLClient
db_v1 = SQLClient("sqlite://")
# sqlservice 2.0
from sqlservice import Database
db = Database("sqlite://")
The old ``SQLClient`` class was essentially a singleton session instance that used a session that was threadlocal. Using ``db_v1.session`` within the same thread would always refer to the same session instance. Having two or more sessions generated from the same ``SQLClient.engine`` in a single thread wasn't supported. It also had many proxy properties to the underlying SQLAlchemy Session object so that ``db_v1.session.`` could be shortened to ``db_v1.``.
The new ``Database`` class is more like a factory wrapper around the underlying SQLAlchemy Engine that better supports the SQLAlchemy 2.0 session management usage patterns. SQLAlchemy Session and Connection instances are created through ``Database`` context-managers. There is no longer a session instance maintained within a ``Database`` instance. Additional Session level features are instead added to the new ``sqlservice.Session`` class that extends SQLAlchemy's Session class.
Executing a Query
+++++++++++++++++
The differences between executing queries using connections and sessions are minimal.
Using sqlservice 1.x:
.. code-block:: python
import sqlalchemy as sa
# 1.x
# using Connection.execute
with db_v1.engine.connect() as conn:
conn.execute("SELECT 1")
# using Connection transaction
with db_v1.engine.begin() as conn:
conn.execute("SELECT 1")
# COMMIT emitted after context manager exits
# using Session.execute
db_v1.execute("SELECT 1")
# using Session transaction
with db_v1.transaction():
db_v1.session.execute("SELECT 1")
Using sqlservice 2.0:
.. code-block:: python
# 2.0
# using Connection.execute
with db.connect() as conn:
conn.execute(sa.text("SELECT 1"))
# using Connection transaction
with db.engine.begin() as conn:
conn.execute(sa.text("SELECT 1"))
# COMMIT emitted after context manager exits
# using Session.execute
with db.session() as session:
session.execute(sa.text("SELECT 1"))
# using Session transaction
with db.begin() as session:
session.execute(sa.select(1))
Managing an ORM Transaction
+++++++++++++++++++++++++++
The custom transaction method/decorator available in sqlservice 1.x has been removed in favor of using a SQLAlchemy Session object directly.
Using sqlservice 1.x:
.. code-block:: python
with db_v1.transaction():
user1 = User()
user2 = User()
db_v1.add(user1)
db_v1.add(user2)
db_v1.add_all([User(), User()])
Using sqlservice 2.0:
.. code-block:: python
with db.begin() as session:
user1 = User()
user2 = User()
session.add(user1)
session.add(user2)
session.add_all([User(), User()])
Save Method Moved to Session & before, after, and identity Arguments Removed
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
The ``SQLClient.save`` method has moved to ``sqlservice.Session``.
Using sqlservice 1.x:
.. code-block:: python
user = User()
db_v1.save(
user,
before=lambda: 'execute before saving',
after=lambda: 'execute after saving',
identity=lambda model: 'return custom identifier for model'
)
Using sqlservice 2.0:
.. code-block:: python
user = User()
with db.begin() as session:
session.save(user)
# before, after, and identity removed
Bulk Save Functionality Moved to New save_all Method
++++++++++++++++++++++++++++++++++++++++++++++++++++
Bulk saving of models is now done with ``sqlservice.Session.save_all``.
Using sqlservice 1.x:
.. code-block:: python
users = [User(), User(), User()]
db_v1.save(users)
Using sqlservice 2.0:
.. code-block:: python
users = [User(), User(), User()]
with db.begin() as session:
session.save_all(users)
Query Class Removed
-------------------
Since SQLAlchemy 1.4, the ``session.query`` pattern is considered legacy and will be removed in its 2.0 version. Similarly, it has also been removed in sqlservice 2.0.
Model Class is Leaner
---------------------
In sqlservice 1.x, a model could be instantiated/updated using either a single dictionary argument or multiple keyword-arguments. Extra dictionary keys or keyword-arguments not mapped to the class were ignored. This is has changed in sqlservice 2.0:
- ``Model.__init__()`` and ``Model.set()`` (formerly ``Model.update()``) only support keyword-arguments. Passing a dictionary instance is no longer supported. **breaking change**
- However, creation of a model using a dictionary can be done using ``Model.from_dict()``.
- Using extra keyword-arguments or dictionary keys (when using ``Model.from_dict()``) when creating or updating a model will now raise an exception. **breaking change**
- Model is no longer scriptable (i.e. ``model_instance["column_name"]`` is not supported). **breaking change**
Other breaking changes:
- ``Model.update()`` renamed to ``Model.set()``. ``Model.update()`` is now a ``classmethod`` that returns a ``sqlalchemy.Update`` instance for use in query building. **breaking change**
- ``Model.identity()`` renamed to ``Model.pk()``. **breaking change**
- ``Model.identity_map()`` removed. **breaking change**
- Class methods that proxied ``sqlalchemy.orm.Mapper`` attributes have been removed. Use ``sqlalchemy.inspect(MyModel)`` directly instead. **breaking change**
- The class attribute ``Model.__dict_args__`` as a way to customize the ``Model.to_dict()`` serialization has been removed. Use of a custom serialization implementation or a serialization library is recommended instead. **breaking change**
Events
------
The event decorators have been made easier to use with the following changes:
- Decorated methods no longer require all event callback arguments to be defined in the method signature. For example, if the sqlalchemy event emitter would send 4 arguments, the sqlservice event-decorated method could define just 1 argument in its function signature and not cause an exception when called.
- The mapper based events (``before_delete``, ``before_insert``, ``before_update``, ``before_save``, ``after_delete``, ``after_insert``, ``after_update``, and ``after_save``) have their callback argument order reversed so that the first argument would correspond to the ``self`` argument of the class. This means that before ``v2``, the callback argument order was ``(mapper, connection, self)`` but in ``v2`` it is ``(self, connection, mapper)``. This was done so that the class method definitions would conform to the standard of having ``self`` as the first argument. **breaking change**