{"id":780,"date":"2014-10-05T18:18:32","date_gmt":"2014-10-05T22:18:32","guid":{"rendered":"http:\/\/bioinfo.iric.ca\/?p=780"},"modified":"2017-04-29T17:27:37","modified_gmt":"2017-04-29T21:27:37","slug":"gestion-des-versions-du-contenu-dune-bd-avec-sqlalchemy","status":"publish","type":"post","link":"https:\/\/bioinfo.iric.ca\/fr\/gestion-des-versions-du-contenu-dune-bd-avec-sqlalchemy\/","title":{"rendered":"Gestion des versions du contenu d&rsquo;une BD avec SQLAlchemy"},"content":{"rendered":"<div class=\"fusion-fullwidth fullwidth-box fusion-builder-row-1 hundred-percent-fullwidth non-hundred-percent-height-scrolling\" style=\"--awb-border-radius-top-left:0px;--awb-border-radius-top-right:0px;--awb-border-radius-bottom-right:0px;--awb-border-radius-bottom-left:0px;--awb-overflow:visible;--awb-flex-wrap:wrap;\" ><div class=\"fusion-builder-row fusion-row\"><div class=\"fusion-layout-column fusion_builder_column fusion-builder-column-0 fusion_builder_column_1_1 1_1 fusion-one-full fusion-column-first fusion-column-last fusion-column-no-min-height\" style=\"--awb-bg-size:cover;--awb-margin-bottom:0px;\"><div class=\"fusion-column-wrapper fusion-flex-column-wrapper-legacy\"><div class=\"fusion-text fusion-text-1\"><p>Une des fonctionnalit\u00e9s phare requises d&rsquo;un SGL est la sauvegarde de l&rsquo;historique des changements appliqu\u00e9s aux donn\u00e9es stor\u00e9es dans la base de donn\u00e9es sous-jacente. Ceci peut repr\u00e9senter une fonctionnalit\u00e9 non triviale \u00e0 impl\u00e9menter et\/ou d\u00e9ployer et il existe certainement plusieurs visions de la forme que cette impl\u00e9mentation devrait prendre.<br \/>\nHeureusement pour tous les fans de <a href=\"http:\/\/www.sqlalchemy.org\/\">SQLAlchemy<\/a>, une solution pr\u00eate \u00e0 l&rsquo;usage est sugg\u00e9r\u00e9e sur la <a href=\"http:\/\/docs.sqlalchemy.org\/en\/rel_0_9\/orm\/examples.html#module-examples.versioned_history\">page des exemples de l&rsquo;ORM<\/a> (en anglais seulement). Bien que la page d&rsquo;exemple sugg\u00e8re diff\u00e9rents types d&rsquo;impl\u00e9mentation, ma pr\u00e9f\u00e9r\u00e9e demeure <em>Versioning with a History Table<\/em>.<\/p>\n<p>Elle est incroyablement simple \u00e0 mettre en place et son utilisation subs\u00e9quente ne requiert l&rsquo;ajout d&rsquo;aucune ligne de code&#8230; Fantastique !<br \/>\nCette impl\u00e9mentation se pr\u00e9sente sous la forme d&rsquo;un <a href=\"http:\/\/en.wikipedia.org\/wiki\/Mixin\">mixin<\/a> con\u00e7u pour fonctionner avec la technique de d\u00e9finition des objet nomm\u00e9e \u00ab\u00a0declarative\u00a0\u00bb. C&rsquo;est cette technique qui est employ\u00e9e dans l&rsquo;exemple ci-bas. Si vous travaillez avec du code un peu plus vieux et faisant usage des mappeurs classiques de SQLAlchemy, un ensemble de fonctions est disponible pour assurer les m\u00eame fonctionnalit\u00e9s.<\/p>\n<blockquote>\n<p>Incroyablement simple \u00e0 mettre en place, son utilisation subs\u00e9quente ne requiert l&rsquo;ajout d&rsquo;aucune ligne de code.<\/p>\n<\/blockquote>\n<p>Laissez-moi vous d\u00e9montrer sa mise en place \u00e0 l&rsquo;aide d&rsquo;un exemple:<\/p>\n<p>En gros, il ne faut qu&rsquo;importer le <em>mixin<\/em> et un <em>wrapper<\/em> pour la session SQL, ajouter le <em>mixin<\/em> \u00e0 notre d\u00e9claration de classe et envelopper votre session dans une <em>versioned_session<\/em>.<\/p>\n<\/div><div class=\"fusion-clearfix\"><\/div><\/div><\/div><div class=\"fusion-layout-column fusion_builder_column fusion-builder-column-1 fusion_builder_column_1_2 1_2 fusion-one-half fusion-column-first\" style=\"--awb-bg-size:cover;width:48%; margin-right: 4%;\"><div class=\"fusion-column-wrapper fusion-flex-column-wrapper-legacy\"><div class=\"fusion-text fusion-text-2\"><p>Cet extrait de code:<\/p>\n<pre><code class=\"python\">\r\n\r\n\r\n\r\n\r\n\r\n\r\nBase = declarative_base()\r\n\r\nclass MaClasse(Base):\r\n    __tablename__ = 'ma_table'\r\n\r\n    id = Column(Integer, primary_key=True)\r\n    nom = Column(String(50))\r\n\r\n\r\nSession = sessionmaker(bind=engine)\r\n\r\n\r\n\r\n\r\nsess = Session()\r\n# Faites quelque chose avec votre session\r\n<\/code><\/pre>\n<\/div><div class=\"fusion-clearfix\"><\/div><\/div><\/div><div class=\"fusion-layout-column fusion_builder_column fusion-builder-column-2 fusion_builder_column_1_2 1_2 fusion-one-half fusion-column-last\" style=\"--awb-bg-size:cover;width:48%;\"><div class=\"fusion-column-wrapper fusion-flex-column-wrapper-legacy\"><div class=\"fusion-text fusion-text-3\"><p>Devient:<\/p>\n<pre><code class=\"python\">\r\n# Ajout des d\u00e9clarations d'import n\u00e9cessaires\r\nfrom history_meta import (\r\nVersioned, \r\nversioned_session\r\n)\r\n\r\nBase = declarative_base()\r\n\r\nclass MaClasse(Versioned, Base):\r\n    __tablename__ = 'ma_table'\r\n\r\n    id = Column(Integer, primary_key=True)\r\n    nom = Column(String(50))\r\n\r\n\r\nSession = sessionmaker(bind=engine)\r\n\r\n# enveloppez votre session dans une <i>versioned_session<\/i>\r\nversioned_session(Session)\r\n\r\nsess = Session()\r\n# Faites quelque chose avec votre session\r\n<\/code><\/pre>\n<\/div><div class=\"fusion-clearfix\"><\/div><\/div><\/div><div class=\"fusion-layout-column fusion_builder_column fusion-builder-column-3 fusion_builder_column_1_1 1_1 fusion-one-full fusion-column-first fusion-column-last fusion-column-no-min-height\" style=\"--awb-bg-size:cover;--awb-margin-bottom:0px;\"><div class=\"fusion-column-wrapper fusion-flex-column-wrapper-legacy\"><div class=\"fusion-text fusion-text-4\"><p>Facile, n&rsquo;est-ce pas ?!<br \/>\nL&rsquo;ajout du mixin dans la d\u00e9claration des objets instancie des tables \u00ab\u00a0soeurs\u00a0\u00bb (avec \u00ab\u00a0_history\u00a0\u00bb d&rsquo;ajout\u00e9 \u00e0 leur nom) pour chaque objet <em>Versioned<\/em> d\u00e9clar\u00e9. Ces tables contiendront une copie compl\u00e8te des attributs de la table d&rsquo;origine en plus d&rsquo;un attribut <em>version<\/em> (essentiellement un entier auto incr\u00e9mental) et, optionnellement, un attribut <em>changed<\/em> de type utc_timestamp.<\/p>\n<p>Une fois ces petits ajouts effectu\u00e9s, chaque changement ou suppression appliqu\u00e9 sur un objet occasionnera une \u00e9criture de l&rsquo;objet *avant* sa modification dans la table soeur \u00ab\u00a0_history\u00a0\u00bb accompagn\u00e9 d&rsquo;une incr\u00e9mentation du champ \u00ab\u00a0version\u00a0\u00bb. Qui plus est: toute la cuisine est automatiquement g\u00e9r\u00e9e par l&rsquo;ORM, sans l&rsquo;ajout d&rsquo;une seule ligne de code suppl\u00e9mentaire.<\/p>\n<p>Alors comment fait-on pour acc\u00e9der \u00e0 l&rsquo;historique d&rsquo;une entr\u00e9e de la BD ? Rien de plus simple ! Les lignes de code suivantes expliquent comment instancier une classe correspondant \u00e0 l&rsquo;historique de la classe soeur. Celle-ci s&rsquo;utilise ensuite comme un objet SQLAlchemy classique:<\/p>\n<pre><code class=\"python\">\r\nHistoriqueDeMaClasse = MaClasse.__history_mapper__.class_\r\netats_precedents = HistoriqueDeMaClasse.filter(HistoriqueDeMaClasse.id == un_id)\\\r\n                  .order_by(HistoriqueDeMaClasse.version).all()\r\n\r\n## Faites quelque chose avec les \u00e9tats pr\u00e9c\u00e9dents de votre objet :)\r\n<\/code><\/pre>\n<p>Merveilleux, non ?<br \/>\nC&rsquo;est maintenant le temps de vous mettre au boulot et de d\u00e9ployer des objets avec historique dans tous les projets qui pourraient en b\u00e9n\u00e9ficier !<\/p>\n<\/div><div class=\"fusion-clearfix\"><\/div><\/div><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[122,88,26],"tags":[155,154,153,38],"class_list":["post-780","post","type-post","status-publish","format-standard","hentry","category-database","category-informatique","category-langage-python","tag-developpement-web","tag-mor","tag-sgl","tag-sqlalchemy"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/posts\/780","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/comments?post=780"}],"version-history":[{"count":20,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/posts\/780\/revisions"}],"predecessor-version":[{"id":2751,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/posts\/780\/revisions\/2751"}],"wp:attachment":[{"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/media?parent=780"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/categories?post=780"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/tags?post=780"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}