diff options
Diffstat (limited to 'buildbot_gentoo_ci/db')
-rw-r--r-- | buildbot_gentoo_ci/db/builds.py | 184 | ||||
-rw-r--r-- | buildbot_gentoo_ci/db/connector.py | 6 | ||||
-rw-r--r-- | buildbot_gentoo_ci/db/model.py | 55 | ||||
-rw-r--r-- | buildbot_gentoo_ci/db/packages.py | 113 | ||||
-rw-r--r-- | buildbot_gentoo_ci/db/versions.py | 174 |
5 files changed, 318 insertions, 214 deletions
diff --git a/buildbot_gentoo_ci/db/builds.py b/buildbot_gentoo_ci/db/builds.py index 21adcc0..47413a3 100644 --- a/buildbot_gentoo_ci/db/builds.py +++ b/buildbot_gentoo_ci/db/builds.py @@ -15,7 +15,11 @@ # Copyright Buildbot Team Members # Origins: buildbot.db.* # Modifyed by Gentoo Authors. -# Copyright 2023 Gentoo Authors +# Copyright 2024 Gentoo Authors + +from __future__ import annotations +from dataclasses import dataclass +from typing import TYPE_CHECKING import uuid import sqlalchemy as sa @@ -23,94 +27,134 @@ import sqlalchemy as sa from twisted.internet import defer from buildbot.db import base +from buildbot.warnings import warn_deprecated + +if TYPE_CHECKING: + import datetime + +@dataclass +class ProjectsBuildsModel: + id : int + build_id : int + project_uuid : str + version_uuid : str + buildbot_build_id : int + bug_id : int + status : str + requested : bool + created_at : datetime.datetime | None + updated_at : datetime.datetime | None + deleted : bool + deleted_at: datetime.datetime | None + + # For backward compatibility + def __getitem__(self, key: str): + warn_deprecated( + '4.1.0', + ( + 'VersionsConnectorComponent getVersionByName, ' + 'getBuildByNumber, getPrevSuccessfulBuild, ' + 'getBuildsForChange, getBuilds, ' + '_getRecentBuilds, and _getBuild ' + 'no longer return Build as dictionnaries. ' + 'Usage of [] accessor is deprecated: please access the member directly' + ), + ) + + if hasattr(self, key): + return getattr(self, key) + + raise KeyError(key) + +def _db2data_ProjectsBuilds(model: ProjectsBuildsModel): + if model is None: + return None + return { + 'id' : model.id, + 'build_id' : model.build_id, + 'project_uuid' : model.project_uuid, + 'version_uuid' : model.version_uuid, + 'buildbot_build_id' : model.buildbot_build_id, + 'bug_id' : model.bug_id, + 'status' : model.status, + 'requested' : model.requested, + 'created_at' : model.created_at, + 'updated_at' : model.updated_at, + 'deleted' : model.deleted, + 'deleted_at' : model.deleted_at + } class BuildsConnectorComponent(base.DBConnectorComponent): - #@defer.inlineCallbacks def addBuild(self, project_build_data): - created_at = int(self.master.reactor.seconds()) - def thd(conn, no_recurse=False): - tbl = self.db.model.projects_builds + def thd(conn): # get the highest current number - r = conn.execute(sa.select([sa.func.max(tbl.c.build_id)], - whereclause=(tbl.c.project_uuid == project_build_data['project_uuid']))) + tbl = self.db.model.projects_builds + r = conn.execute( + sa.select(sa.func.max(tbl.c.build_id)).where(tbl.c.project_uuid == project_build_data['project_uuid']) + ) number = r.scalar() new_number = 1 if number is None else number + 1 + insert_row = { + 'project_uuid' : project_build_data['project_uuid'], + 'version_uuid' : project_build_data['version_uuid'], + 'status' : project_build_data['status'], + 'requested' : project_build_data['requested'], + 'created_at' : int(self.master.reactor.seconds()), + 'buildbot_build_id' : 0, + 'build_id' : new_number, + 'bug_id' : 0, + } try: - q = tbl.insert() - r = conn.execute(q, dict(project_uuid=project_build_data['project_uuid'], - version_uuid=project_build_data['version_uuid'], - status=project_build_data['status'], - requested=project_build_data['requested'], - created_at=created_at, - buildbot_build_id=0, - build_id=new_number, - bug_id=0 - )) + r = conn.execute(tbl.insert(), insert_row) + conn.commit() except (sa.exc.IntegrityError, sa.exc.ProgrammingError): - id = None - new_number = None - else: - id = r.inserted_primary_key[0] - return id, new_number + conn.rollback() + return None, None + return r.inserted_primary_key[0], new_number return self.db.pool.do(thd) - @defer.inlineCallbacks def setStatusBuilds(self, id, status): - updated_at = int(self.master.reactor.seconds()) - def thd(conn, no_recurse=False): - tbl = self.db.model.projects_builds - q = tbl.update() - q = q.where(tbl.c.id == id) - conn.execute(q, updated_at=updated_at, - status=status) - yield self.db.pool.do(thd) - - @defer.inlineCallbacks + def thd(conn): + tbl = self.db.model.projects_builds + q = tbl.update().where(tbl.c.id == id) + conn.execute(q.values(updated_at=int(self.master.reactor.seconds()), status=status)) + return self.db.pool.do(thd) + def setBuildbotBuildIdBuilds(self, id, buildbot_build_id): - updated_at = int(self.master.reactor.seconds()) - def thd(conn, no_recurse=False): - tbl = self.db.model.projects_builds - q = tbl.update() - q = q.where(tbl.c.id == id) - conn.execute(q, updated_at=updated_at, - buildbot_build_id=buildbot_build_id) - yield self.db.pool.do(thd) - - @defer.inlineCallbacks + def thd(conn): + tbl = self.db.model.projects_builds + q = tbl.update().where(tbl.c.id == id) + conn.execute(q.values(updated_at=int(self.master.reactor.seconds()), buildbot_build_id=buildbot_build_id)) + return self.db.pool.do(thd) + def setBugIdBuilds(self, id, bug_id): - updated_at = int(self.master.reactor.seconds()) - def thd(conn, no_recurse=False): - tbl = self.db.model.projects_builds - q = tbl.update() - q = q.where(tbl.c.id == id) - conn.execute(q, updated_at=updated_at, bug_id=bug_id) - yield self.db.pool.do(thd) - - @defer.inlineCallbacks - def getBuildsByVersionUuid(self, uuid): def thd(conn): tbl = self.db.model.projects_builds + q = tbl.update().where(tbl.c.id == id) + conn.execute(q.values(updated_at=int(self.master.reactor.seconds()), bug_id=bug_id)) + return self.db.pool.do(thd) + + def getBuildsByVersionUuid(self, uuid) -> defer.Deferred[list[ProjectsBuildsModel]]: + def thd(conn) -> list[ProjectsBuildsModel]: + tbl = self.db.model.projects_builds q = tbl.select() q = q.where(tbl.c.version_uuid == uuid) res = conn.execute(q) - row = res.fetchone() - return [self._row2dict(conn, row) - for row in conn.execute(q).fetchall()] - res = yield self.db.pool.do(thd) - return res - - @defer.inlineCallbacks - def removeBuild(self, id): - def thd(conn, no_recurse=False): - tbl = self.db.model.projects_builds - q = tbl.delete() - q = q.where(tbl.c.id == id) - conn.execute(q) - yield self.db.pool.do(thd) - - def _row2dict(self, conn, row): - return dict( + return list(self._model_from_row(row) for row in res.fetchall()) + return self.db.pool.do(thd) + + def removeBuild(self, id: int) -> defer.Deferred[None]: + def thd(conn) -> None: + tbl = self.db.model.projects_builds + q = tbl.delete().where(tbl.c.id == id) + res = conn.execute(q) + conn.commit() + res.close() + return self.db.pool.do(thd) + + def _model_from_row(self, row): + return ProjectsBuildsModel( id=row.id, build_id=row.build_id, project_uuid=row.project_uuid, diff --git a/buildbot_gentoo_ci/db/connector.py b/buildbot_gentoo_ci/db/connector.py index 0cc7884..65d74fc 100644 --- a/buildbot_gentoo_ci/db/connector.py +++ b/buildbot_gentoo_ci/db/connector.py @@ -104,10 +104,8 @@ class DBConnector(service.ReconfigurableServiceMixin, log.msg(f"Setting up database with URL {repr(util.stripUrlPassword(db_url))}") # set up the engine and pool - self._engine = enginestrategy.create_engine(db_url, - basedir=self.basedir) - self.pool = pool.DBThreadPool( - self._engine, reactor=self.master.reactor, verbose=verbose) + self._engine = enginestrategy.create_engine(db_url, basedir=self.basedir) + self.pool = pool.DBThreadPool(self._engine, reactor=self.master.reactor, verbose=verbose) # make sure the db is up to date, unless specifically asked not to if check_version: diff --git a/buildbot_gentoo_ci/db/model.py b/buildbot_gentoo_ci/db/model.py index 448e67f..d5364c7 100644 --- a/buildbot_gentoo_ci/db/model.py +++ b/buildbot_gentoo_ci/db/model.py @@ -17,8 +17,11 @@ # Modifyed by Gentoo Authors. # Copyright 2024 Gentoo Authors -import uuid +from __future__ import annotations + +from typing import TYPE_CHECKING +import uuid import alembic import alembic.config import sqlalchemy as sa @@ -32,26 +35,9 @@ from buildbot.db.migrate_utils import test_unicode from buildbot.db.types.json import JsonObject from buildbot.util import sautils - -class UpgradeFromBefore0p9Error(Exception): - - def __init__(self): - message = """You are trying to upgrade a buildbot 0.8.x master to buildbot 0.9.x or newer. - This is not supported. Please start from a clean database - http://docs.buildbot.net/latest/manual/upgrading/0.9-upgrade.html""" - # Call the base class constructor with the parameters it needs - super().__init__(message) - - -class UpgradeFromBefore3p0Error(Exception): - - def __init__(self): - message = """You are trying to upgrade to Buildbot 3.0 or newer from Buildbot 2.x or older. - This is only supported via an intermediate upgrade to newest Buildbot 2.10.x that is - available. Please first upgrade to 2.10.x and then try to upgrade to this version. - http://docs.buildbot.net/latest/manual/upgrading/3.0-upgrade.html""" - super().__init__(message) - +if TYPE_CHECKING: + from sqlalchemy.engine.base import Connectable as SQLAConnection + from sqlalchemy.engine.reflection import Inspector class Model(base.DBConnectorComponent): @@ -364,6 +350,7 @@ class Model(base.DBConnectorComponent): sa.Column('change_id', sa.Integer, nullable=True, default=0), sa.Column('deleted', sa.Boolean, default=False), sa.Column('deleted_at', sa.Integer, nullable=True), + sa.Column('created_at', sa.Integer, nullable=True), ) versions_keywords = sautils.Table( @@ -496,13 +483,9 @@ class Model(base.DBConnectorComponent): config_path = util.sibpath(__file__, "migrations/alembic.ini") - def table_exists(self, conn, table): - try: - r = conn.execute(f"select * from {table} limit 1") - r.close() - return True - except Exception: - return False + def table_exists(self, conn: SQLAConnection, table: str): + inspector: Inspector = sa.inspect(conn.engine) + return inspector.has_table(table) def migrate_get_version(self, conn): r = conn.execute("select version from migrate_version limit 1") @@ -550,19 +533,10 @@ class Model(base.DBConnectorComponent): alembic_scripts = self.alembic_get_scripts() current_script_rev_head = alembic_scripts.get_current_head() - #if self.table_exists(conn, 'version'): - # raise UpgradeFromBefore0p9Error() - if self.table_exists(conn, 'migrate_version'): version = self.migrate_get_version(conn) - #if version < 40: - # raise UpgradeFromBefore0p9Error() - - last_sqlalchemy_migrate_version = 0 - if version != last_sqlalchemy_migrate_version: - raise UpgradeFromBefore3p0Error() - + last_sqlalchemy_migrate_version = 58 self.alembic_stamp(conn, alembic_scripts, alembic_scripts.get_base()) conn.execute('drop table migrate_version') @@ -576,6 +550,11 @@ class Model(base.DBConnectorComponent): self.alembic_stamp(conn, alembic_scripts, current_script_rev_head) return + def upgrade(rev, context): + log.msg(f'Upgrading from {rev} to {current_script_rev_head}') + return alembic_scripts._upgrade_revs(current_script_rev_head, rev) + + context = alembic.runtime.migration.MigrationContext.configure(conn) current_rev = context.get_current_revision() diff --git a/buildbot_gentoo_ci/db/packages.py b/buildbot_gentoo_ci/db/packages.py index ad4a244..2e29c02 100644 --- a/buildbot_gentoo_ci/db/packages.py +++ b/buildbot_gentoo_ci/db/packages.py @@ -15,7 +15,11 @@ # Copyright Buildbot Team Members # Origins: buildbot.db.* # Modifyed by Gentoo Authors. -# Copyright 2021 Gentoo Authors +# Copyright 2024 Gentoo Authors + +from __future__ import annotations +from dataclasses import dataclass +from typing import TYPE_CHECKING import uuid import sqlalchemy as sa @@ -23,12 +27,42 @@ import sqlalchemy as sa from twisted.internet import defer from buildbot.db import base +from buildbot.warnings import warn_deprecated + +if TYPE_CHECKING: + import datetime + +@dataclass +class PackageModel: + uuid : str + name : str + category_uuid : str + repository_uuid : str + deleted : bool + deleted_at : datetime.datetime | None + + # For backward compatibility + def __getitem__(self, key: str): + if hasattr(self, key): + return getattr(self, key) + raise KeyError(key) + +def _db2data_Package(model: PackageModel): + if model is None: + return None + return { + 'uuid' : model.uuid, + 'name' : model.name, + 'category_uuid' : model.category_uuid, + 'repository_uuid' : model.repository_uuid, + 'deleted' : model.deleted, + 'deleted_at' : model.deleted_at, + } class PackagesConnectorComponent(base.DBConnectorComponent): - @defer.inlineCallbacks - def getPackageByName(self, name, c_uuid, repo_uuid, deleted=False): - def thd(conn): + def getPackageByName(self, name: str, c_uuid: str, repo_uuid: str, deleted: bool | None = False) -> defer.Deferred[PackageModel | None]: + def thd(conn) -> PackageModel | None: tbl = self.db.model.packages q = tbl.select() q = q.where(tbl.c.name == name) @@ -37,52 +71,54 @@ class PackagesConnectorComponent(base.DBConnectorComponent): q = q.where(tbl.c.repository_uuid == repo_uuid) res = conn.execute(q) row = res.fetchone() - if not row: - return None - return self._row2dict(conn, row) - res = yield self.db.pool.do(thd) - return res + rv = None + if row: + rv = self._model_from_row_PackageModel(row) + res.close() + return rv + return self.db.pool.do(thd) - @defer.inlineCallbacks - def getPackageByUuid(self, uuid): - def thd(conn): + def getPackageByUuid(self, uuid: str) -> defer.Deferred[PackageModel | None]: + def thd(conn) -> PackageModel | None: tbl = self.db.model.packages q = tbl.select() q = q.where(tbl.c.uuid == uuid) res = conn.execute(q) row = res.fetchone() - if not row: - return None - return self._row2dict(conn, row) - res = yield self.db.pool.do(thd) - return res + rv = None + if row: + rv = self._model_from_row_PackageModel(row) + res.close() + return rv + return self.db.pool.do(thd) - @defer.inlineCallbacks - def addPackage(self, name, repository_uuid, category_uuid): - def thd(conn, no_recurse=False): + def addPackage(self, name: str, repository_uuid: str, category_uuid: str) -> defer.Deferred[str]: + def thd(conn) -> str: + insert_row = { + 'name' : name, + 'repository_uuid' : repository_uuid, + 'category_uuid' : category_uuid, + 'deleted_at' : 0, + 'deleted' : False, + } try: - tbl = self.db.model.packages - q = tbl.insert() - r = conn.execute(q, dict(name=name, - repository_uuid=repository_uuid, - category_uuid=category_uuid)) - except Exception as e: - print(type(e)) - print(e.args) - print(e) - uuid = None + r = conn.execute(self.db.model.packages.insert(), insert_row) + conn.commit() + except (sa.exc.IntegrityError, sa.exc.ProgrammingError): + conn.rollback() + return None else: - uuid = r.inserted_primary_key[0] - return uuid - res = yield self.db.pool.do(thd) - return res + return r.inserted_primary_key[0] + return self.db.pool.do(thd) - def _row2dict(self, conn, row): - return dict( + def _model_from_row_PackageModel(self, row): + return PackageModel( uuid=row.uuid, name=row.name, repository_uuid=row.repository_uuid, - category_uuid=row.category_uuid + category_uuid=row.category_uuid, + deleted=row.deleted, + deleted_at=row.deleted_at, ) @defer.inlineCallbacks @@ -209,8 +245,7 @@ class PackagesConnectorComponent(base.DBConnectorComponent): def delPackageEmail(self, package_uuid): def thd(conn, no_recurse=False): tbl = self.db.model.packages_emails - conn.execute(tbl.delete( - whereclause=((tbl.c.package_uuid == package_uuid)))) + conn.execute(tbl.delete().where(tbl.c.package_uuid == package_uuid)) res = yield self.db.pool.do(thd) return res diff --git a/buildbot_gentoo_ci/db/versions.py b/buildbot_gentoo_ci/db/versions.py index afb9e52..60946c5 100644 --- a/buildbot_gentoo_ci/db/versions.py +++ b/buildbot_gentoo_ci/db/versions.py @@ -15,7 +15,11 @@ # Copyright Buildbot Team Members # Origins: buildbot.db.* # Modifyed by Gentoo Authors. -# Copyright 2021 Gentoo Authors +# Copyright 2024 Gentoo Authors + +from __future__ import annotations +from dataclasses import dataclass +from typing import TYPE_CHECKING import uuid import sqlalchemy as sa @@ -23,11 +27,48 @@ import sqlalchemy as sa from twisted.internet import defer from buildbot.db import base +from buildbot.warnings import warn_deprecated + +if TYPE_CHECKING: + import datetime + +@dataclass +class VersionModel: + uuid: str + name: str + package_uuid: str + file_hash: str + commit_id: str + change_id: int + created_at: datetime.datetime | None + deleted: bool + deleted_at: datetime.datetime | None + + # For backward compatibility + def __getitem__(self, key: str): + if hasattr(self, key): + return getattr(self, key) + raise KeyError(key) + +def _db2data_Version(model: VersionModel): + if model is None: + return None + return { + 'uuid' : model.uuid, + 'name' : model.name, + 'package_uuid' : model.package_uuid, + 'file_hash' : model.file_hash, + 'commit_id' : model.commit_id, + 'change_id' : model.change_id, + 'deleted' : model.deleted, + 'deleted_at' : model.deleted_at, + 'created_at' : model.created_at, + } + class VersionsConnectorComponent(base.DBConnectorComponent): - @defer.inlineCallbacks - def getVersionByName(self, name, p_uuid, deleted=False): - def thd(conn): + def getVersionByName(self, name: str, p_uuid: str, deleted: bool | None = False) -> defer.Deferred[VersionModel | None]: + def thd(conn) -> VersionModel | None: tbl = self.db.model.versions q = tbl.select() q = q.where(tbl.c.name == name) @@ -35,56 +76,57 @@ class VersionsConnectorComponent(base.DBConnectorComponent): q = q.where(tbl.c.deleted == deleted) res = conn.execute(q) row = res.fetchone() - if not row: - return None - return self._row2dict(conn, row) - res = yield self.db.pool.do(thd) - return res - - @defer.inlineCallbacks - def getVersionByUuid(self, uuid): - def thd(conn): + rv = None + if row: + rv = self._model_from_row_VersionModel(row) + res.close() + return rv + return self.db.pool.do(thd) + + def getVersionByUuid(self, uuid: str) -> defer.Deferred[VersionModel | None]: + def thd(conn) -> VersionModel | None: tbl = self.db.model.versions q = tbl.select() q = q.where(tbl.c.uuid == uuid) res = conn.execute(q) row = res.fetchone() - if not row: - return None - return self._row2dict(conn, row) - res = yield self.db.pool.do(thd) - return res - - @defer.inlineCallbacks - def addVersion(self, name, package_uuid, file_hash, commit_id, change_id): - def thd(conn, no_recurse=False): + rv = None + if row: + rv = self._model_from_row_VersionModel(row) + res.close() + return rv + return self.db.pool.do(thd) + + def addVersion(self, name: str, package_uuid: str, file_hash: str, commit_id: int, change_id: int) -> defer.Deferred[str]: + def thd(conn) -> str: + insert_row = { + 'name': name, + 'package_uuid' : package_uuid, + 'file_hash' : file_hash, + 'commit_id' : commit_id, + 'change_id' : change_id, + 'created_at' : int(self.master.reactor.seconds()), + 'deleted_at' : 0, + 'deleted' : False, + } try: - tbl = self.db.model.versions - q = tbl.insert() - r = conn.execute(q, dict(name=name, - package_uuid=package_uuid, - file_hash=file_hash, - commit_id=commit_id, - change_id=change_id)) + r = conn.execute(self.db.model.versions.insert(), insert_row) + conn.commit() except (sa.exc.IntegrityError, sa.exc.ProgrammingError): - uuid = None + conn.rollback() + return None else: - uuid = r.inserted_primary_key[0] - return uuid - res = yield self.db.pool.do(thd) - return res + return r.inserted_primary_key[0] + return self.db.pool.do(thd) - @defer.inlineCallbacks def delVersion(self, uuid): - deleted_at = int(self.master.reactor.seconds()) - def thd(conn, no_recurse=False): - - tbl = self.db.model.versions - q = tbl.update() - q = q.where(tbl.c.uuid == uuid) - conn.execute(q, deleted=True, - deleted_at=deleted_at) - yield self.db.pool.do(thd) + def thd(conn): + tbl = self.db.model.versions + q = tbl.update().where(tbl.c.uuid == uuid) + res = conn.execute(q.values(deleted=True, deleted_at=int(self.master.reactor.seconds()))) + #conn.commit() + res.close() + return self.db.pool.do(thd) @defer.inlineCallbacks def addKeyword(self, version_uuid, keyword_id, status): @@ -95,6 +137,7 @@ class VersionsConnectorComponent(base.DBConnectorComponent): r = conn.execute(q, dict(version_uuid=version_uuid, keyword_id=keyword_id, status=status)) + conn.commit() except (sa.exc.IntegrityError, sa.exc.ProgrammingError): uuid = None else: @@ -112,6 +155,7 @@ class VersionsConnectorComponent(base.DBConnectorComponent): r = conn.execute(q, dict(version_uuid=version_uuid, metadata=metadata, value=value)) + #conn.commit() except (sa.exc.IntegrityError, sa.exc.ProgrammingError): id = None else: @@ -140,41 +184,44 @@ class VersionsConnectorComponent(base.DBConnectorComponent): q = tbl.select() q = q.where(tbl.c.deleted == deleted) q = q.where(tbl.c.package_uuid == p_uuid) - return [self._row2dict(conn, row) + return [self._model_from_row_VersionModel(row) for row in conn.execute(q).fetchall()] res = yield self.db.pool.do(thd) return res - @defer.inlineCallbacks - def removeVersion(self, uuid): - def thd(conn, no_recurse=False): + def removeVersion(self, uuid: str) -> defer.Deferred[None]: + def thd(conn) -> None: tbl = self.db.model.versions q = tbl.delete() q = q.where(tbl.c.uuid == uuid) - conn.execute(q) - yield self.db.pool.do(thd) + res = conn.execute(q) + conn.commit() + res.close() + return self.db.pool.do(thd) - @defer.inlineCallbacks - def removeVersionMetadata(self, version_uuid): - def thd(conn, no_recurse=False): + def removeVersionMetadata(self, version_uuid: str) -> defer.Deferred[None]: + def thd(conn) -> None: tbl = self.db.model.versions_metadata q = tbl.delete() q = q.where(tbl.c.version_uuid == version_uuid) - conn.execute(q) - yield self.db.pool.do(thd) + res = conn.execute(q) + conn.commit() + res.close() + return self.db.pool.do(thd) - @defer.inlineCallbacks - def removeVersionKeyword(self, version_uuid): - def thd(conn, no_recurse=False): + def removeVersionKeyword(self, version_uuid: str) -> defer.Deferred[None]: + def thd(conn) -> None: tbl = self.db.model.versions_keywords q = tbl.delete() q = q.where(tbl.c.version_uuid == version_uuid) - conn.execute(q) - yield self.db.pool.do(thd) + res = conn.execute(q) + conn.commit() + res.close() + return self.db.pool.do(thd) - def _row2dict(self, conn, row): - return dict( + def _model_from_row_VersionModel(self, row): + return VersionModel( uuid=row.uuid, name=row.name, package_uuid=row.package_uuid, @@ -182,7 +229,8 @@ class VersionsConnectorComponent(base.DBConnectorComponent): commit_id=row.commit_id, change_id = row.change_id, deleted=row.deleted, - deleted_at=row.deleted_at + deleted_at=row.deleted_at, + created_at=row.created_at, ) def _row2dict_version_metadata(self, conn, row): |