Skip to content

Commit

Permalink
db: Add BuildModel dataclass
Browse files Browse the repository at this point in the history
  • Loading branch information
tdesveaux committed May 15, 2024
1 parent 8a6c811 commit eaefb2d
Show file tree
Hide file tree
Showing 16 changed files with 350 additions and 290 deletions.
13 changes: 7 additions & 6 deletions master/buildbot/data/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

if TYPE_CHECKING:
from buildbot.db.builders import BuilderModel
from buildbot.db.builds import BuildModel


class EndpointKind(enum.Enum):
Expand Down Expand Up @@ -196,7 +197,7 @@ def __init__(self, master, args) -> None:
# False is used as special value as "not set". None is used as "not exists". This solves
# the problem of multiple database queries in case entity does not exist.
self.step_dict = False
self.build_dict = False
self.build_dict: BuildModel | None | False = False
self.builder_dict: BuilderModel | None | False = False
self.log_dict = False
self.worker_dict = False
Expand All @@ -217,7 +218,7 @@ async def get_step_dict(self):
return None

self.step_dict = await self.master.db.steps.getStep(
buildid=build_dict['id'],
buildid=build_dict.id,
number=self.args.get('step_number'),
name=self.args.get('step_name'),
)
Expand All @@ -234,7 +235,7 @@ async def get_step_dict(self):
return self.step_dict

@async_to_deferred
async def get_build_dict(self):
async def get_build_dict(self) -> BuilderModel | None:
if self.build_dict is not False:
return self.build_dict

Expand Down Expand Up @@ -271,7 +272,7 @@ async def get_build_id(self):
build_dict = await self.get_build_dict()
if build_dict is None:
return None
return build_dict['id']
return build_dict.id

@async_to_deferred
async def get_builder_dict(self) -> BuilderModel | None:
Expand All @@ -295,7 +296,7 @@ async def get_builder_dict(self) -> BuilderModel | None:
# fallback when there's only indirect information
build_dict = await self.get_build_dict()
if build_dict is not None:
self.builder_dict = await self.master.db.builders.getBuilder(build_dict['builderid'])
self.builder_dict = await self.master.db.builders.getBuilder(build_dict.builderid)
return self.builder_dict

self.builder_dict = None
Expand Down Expand Up @@ -346,7 +347,7 @@ async def get_worker_dict(self):

build_dict = await self.get_build_dict()
if build_dict is not None:
workerid = build_dict.get('workerid', None)
workerid = build_dict.workerid
if workerid is not None:
self.worker_dict = await self.master.db.workers.getWorker(workerid=workerid)
return self.worker_dict
Expand Down
49 changes: 28 additions & 21 deletions master/buildbot/data/builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,37 @@
#
# Copyright Buildbot Team Members

from __future__ import annotations

from typing import TYPE_CHECKING

from twisted.internet import defer

from buildbot.data import base
from buildbot.data import types
from buildbot.data.resultspec import ResultSpec

if TYPE_CHECKING:
from buildbot.db.builds import BuildModel


def _db2data(model: BuildModel):
return {
'buildid': model.id,
'number': model.number,
'builderid': model.builderid,
'buildrequestid': model.buildrequestid,
'workerid': model.workerid,
'masterid': model.masterid,
'started_at': model.started_at,
'complete_at': model.complete_at,
"locks_duration_s": model.locks_duration_s,
'complete': model.complete_at is not None,
'state_string': model.state_string,
'results': model.results,
'properties': {},
}


class Db2DataMixin:
def _generate_filtered_properties(self, props, filters):
Expand All @@ -42,24 +67,6 @@ def _generate_filtered_properties(self, props, filters):
)
return None

def db2data(self, dbdict):
data = {
'buildid': dbdict['id'],
'number': dbdict['number'],
'builderid': dbdict['builderid'],
'buildrequestid': dbdict['buildrequestid'],
'workerid': dbdict['workerid'],
'masterid': dbdict['masterid'],
'started_at': dbdict['started_at'],
'complete_at': dbdict['complete_at'],
"locks_duration_s": dbdict["locks_duration_s"],
'complete': dbdict['complete_at'] is not None,
'state_string': dbdict['state_string'],
'results': dbdict['results'],
'properties': {},
}
return defer.succeed(data)

fieldMapping = {
'buildid': 'builds.id',
'number': 'builds.number',
Expand Down Expand Up @@ -94,7 +101,7 @@ def get(self, resultSpec, kwargs):
num = kwargs['build_number']
dbdict = yield self.master.db.builds.getBuildByNumber(bldr, num)

data = yield self.db2data(dbdict) if dbdict else None
data = _db2data(dbdict) if dbdict else None
# In some cases, data could be None
if data:
filters = resultSpec.popProperties() if hasattr(resultSpec, 'popProperties') else []
Expand All @@ -116,7 +123,7 @@ def actionStop(self, args, kwargs):
bldr = kwargs['builderid']
num = kwargs['build_number']
dbdict = yield self.master.db.builds.getBuildByNumber(bldr, num)
buildid = dbdict['id']
buildid = dbdict.id
self.master.mq.produce(
("control", "builds", str(buildid), 'stop'),
{"reason": kwargs.get('reason', args.get('reason', 'no reason'))},
Expand Down Expand Up @@ -173,7 +180,7 @@ def get(self, resultSpec, kwargs):

buildscol = []
for b in builds:
data = yield self.db2data(b)
data = _db2data(b)
if kwargs.get('graphql'):
# let the graphql engine manage the properties
del data['properties']
Expand Down
4 changes: 2 additions & 2 deletions master/buildbot/data/logchunks.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def get_info():
if builder_dict is not None:
log_prefix += f'Builder: {builder_dict.name}\n'
if build_dict is not None:
log_prefix += f'Build number: {build_dict["number"]}\n'
log_prefix += f'Build number: {build_dict.number}\n'
if worker_dict is not None:
log_prefix += f'Worker name: {worker_dict["name"]}\n'

Expand All @@ -60,7 +60,7 @@ def get_info():
if builder_dict is not None:
informative_parts += [builder_dict.name]
if build_dict is not None:
informative_parts += ['build', str(build_dict['number'])]
informative_parts += ['build', str(build_dict.number)]
if step_dict is not None:
informative_parts += ['step', step_dict['name']]
informative_parts += ['log', log_dict['slug']]
Expand Down
4 changes: 2 additions & 2 deletions master/buildbot/data/test_result_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def get(self, resultSpec, kwargs):
build_dbdict = yield self.master.db.builds.getBuild(step_dbdict['buildid'])

sets = yield self.master.db.test_result_sets.getTestResultSets(
build_dbdict['builderid'],
build_dbdict.builderid,
buildid=step_dbdict['buildid'],
stepid=kwargs['stepid'],
complete=complete,
Expand All @@ -64,7 +64,7 @@ def get(self, resultSpec, kwargs):
build_dbdict = yield self.master.db.builds.getBuild(kwargs['buildid'])

sets = yield self.master.db.test_result_sets.getTestResultSets(
build_dbdict['builderid'],
build_dbdict.builderid,
buildid=kwargs['buildid'],
complete=complete,
result_spec=resultSpec,
Expand Down
120 changes: 85 additions & 35 deletions master/buildbot/db/builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,65 @@
# Copyright Buildbot Team Members


from __future__ import annotations

import json
from dataclasses import dataclass
from typing import TYPE_CHECKING

import sqlalchemy as sa
from twisted.internet import defer

from buildbot.db import NULL
from buildbot.db import base
from buildbot.util import epoch2datetime
from buildbot.warnings import warn_deprecated

if TYPE_CHECKING:
import datetime
from typing import Sequence

from buildbot.data.resultspec import ResultSpec
from buildbot.db.sourcestamps import SourceStampModel


@dataclass
class BuildModel:
id: int
number: int
builderid: int
buildrequestid: int
workerid: int | None
masterid: int
started_at: datetime.datetime
complete_at: datetime.datetime | None
locks_duration_s: int | None
state_string: str
results: int | None

# For backward compatibility
def __getitem__(self, key: str):
warn_deprecated(
'4.1.0',
(
'BuildsConnectorComponent getBuild, '
'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)


class BuildsConnectorComponent(base.DBConnectorComponent):
# returns a Deferred that returns a value
def _getBuild(self, whereclause):
def thd(conn):
def _getBuild(self, whereclause) -> defer.Deferred[BuildModel | None]:
def thd(conn) -> BuildModel | None:
q = self.db.model.builds.select()
if whereclause is not None:
q = q.where(whereclause)
Expand All @@ -36,24 +81,23 @@ def thd(conn):

rv = None
if row:
rv = self._builddictFromRow(row)
rv = self._model_from_row(row)
res.close()
return rv

return self.db.pool.do(thd)

def getBuild(self, buildid):
def getBuild(self, buildid: int) -> defer.Deferred[BuildModel | None]:
return self._getBuild(self.db.model.builds.c.id == buildid)

def getBuildByNumber(self, builderid, number):
def getBuildByNumber(self, builderid: int, number: int) -> defer.Deferred[BuildModel | None]:
return self._getBuild(
(self.db.model.builds.c.builderid == builderid)
& (self.db.model.builds.c.number == number)
)

# returns a Deferred that returns a value
def _getRecentBuilds(self, whereclause, offset=0, limit=1):
def thd(conn):
def _getRecentBuilds(self, whereclause, offset=0, limit=1) -> defer.Deferred[list[BuildModel]]:
def thd(conn) -> list[BuildModel]:
tbl = self.db.model.builds

q = tbl.select()
Expand All @@ -71,12 +115,14 @@ def thd(conn):
)

res = conn.execute(q)
return list(self._builddictFromRow(row) for row in res.fetchall())
return list(self._model_from_row(row) for row in res.fetchall())

return self.db.pool.do(thd)

@defer.inlineCallbacks
def getPrevSuccessfulBuild(self, builderid, number, ssBuild):
def getPrevSuccessfulBuild(
self, builderid: int, number: int, ssBuild: Sequence[SourceStampModel]
):
gssfb = self.master.db.sourcestamps.getSourceStampsForBuild
rv = None
tbl = self.db.model.builds
Expand All @@ -96,7 +142,7 @@ def getPrevSuccessfulBuild(self, builderid, number, ssBuild):
break
for prevBuild in prevBuilds:
prevssBuild = {
(ss.repository, ss.branch, ss.codebase) for ss in (yield gssfb(prevBuild['id']))
(ss.repository, ss.branch, ss.codebase) for ss in (yield gssfb(prevBuild.id))
}
if prevssBuild == matchssBuild:
# A successful build with the same
Expand All @@ -107,10 +153,10 @@ def getPrevSuccessfulBuild(self, builderid, number, ssBuild):

return rv

def getBuildsForChange(self, changeid):
def getBuildsForChange(self, changeid: int) -> defer.Deferred[list[BuildModel]]:
assert changeid > 0

def thd(conn):
def thd(conn) -> list[BuildModel]:
# Get builds for the change
changes_tbl = self.db.model.changes
bsets_tbl = self.db.model.buildsets
Expand All @@ -131,15 +177,19 @@ def thd(conn):
.where(changes_tbl.c.changeid == changeid)
)
res = conn.execute(q)
return [self._builddictFromRow(row) for row in res.fetchall()]
return [self._model_from_row(row) for row in res.fetchall()]

return self.db.pool.do(thd)

# returns a Deferred that returns a value
def getBuilds(
self, builderid=None, buildrequestid=None, workerid=None, complete=None, resultSpec=None
):
def thd(conn):
self,
builderid: int | None = None,
buildrequestid: int | None = None,
workerid: int | None = None,
complete: bool | None = None,
resultSpec: ResultSpec | None = None,
) -> defer.Deferred[list[BuildModel]]:
def thd(conn) -> list[BuildModel]:
tbl = self.db.model.builds
q = tbl.select()
if builderid is not None:
Expand All @@ -155,10 +205,10 @@ def thd(conn):
q = q.where(tbl.c.complete_at == NULL)

if resultSpec is not None:
return resultSpec.thd_execute(conn, q, self._builddictFromRow)
return resultSpec.thd_execute(conn, q, self._model_from_row)

res = conn.execute(q)
return [self._builddictFromRow(row) for row in res.fetchall()]
return [self._model_from_row(row) for row in res.fetchall()]

return self.db.pool.do(thd)

Expand Down Expand Up @@ -283,17 +333,17 @@ def thd(conn):

yield self.db.pool.do(thd)

def _builddictFromRow(self, row):
return {
"id": row.id,
"number": row.number,
"builderid": row.builderid,
"buildrequestid": row.buildrequestid,
"workerid": row.workerid,
"masterid": row.masterid,
"started_at": epoch2datetime(row.started_at),
"complete_at": epoch2datetime(row.complete_at),
"locks_duration_s": row.locks_duration_s,
"state_string": row.state_string,
"results": row.results,
}
def _model_from_row(self, row):
return BuildModel(
id=row.id,
number=row.number,
builderid=row.builderid,
buildrequestid=row.buildrequestid,
workerid=row.workerid,
masterid=row.masterid,
started_at=epoch2datetime(row.started_at),
complete_at=epoch2datetime(row.complete_at),
locks_duration_s=row.locks_duration_s,
state_string=row.state_string,
results=row.results,
)
Loading

0 comments on commit eaefb2d

Please sign in to comment.