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.
See also
Learn more about the new 2.0 style from the SQLAlchemy 1.4 / 2.0 Tutorial.
New Database Class Replaces SQLClient¶
breaking change
# 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.<method> could be shortened to db_v1.<method>.
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:
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:
# 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:
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:
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:
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:
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:
users = [User(), User(), User()]
db_v1.save(users)
Using sqlservice 2.0:
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__()andModel.set()(formerlyModel.update()) only support keyword-arguments. Passing a dictionary instance is no longer supported. breaking changeHowever, 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 changeModel is no longer scriptable (i.e.
model_instance["column_name"]is not supported). breaking change
Other breaking changes:
Model.update()renamed toModel.set().Model.update()is now aclassmethodthat returns asqlalchemy.Updateinstance for use in query building. breaking changeModel.identity()renamed toModel.pk(). breaking changeModel.identity_map()removed. breaking changeClass methods that proxied
sqlalchemy.orm.Mapperattributes have been removed. Usesqlalchemy.inspect(MyModel)directly instead. breaking changeThe class attribute
Model.__dict_args__as a way to customize theModel.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, andafter_save) have their callback argument order reversed so that the first argument would correspond to theselfargument of the class. This means that beforev2, the callback argument order was(mapper, connection, self)but inv2it is(self, connection, mapper). This was done so that the class method definitions would conform to the standard of havingselfas the first argument. breaking change