pychestrate-core 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,725 @@
1
+ """
2
+ .. module:: orm
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Contains the ORM associated with the akit database storage
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Automation Mojo LLC"
11
+ __credits__ = []
12
+ __version__ = "1.0.0"
13
+
14
+
15
+ import enum
16
+ import json
17
+ import uuid
18
+
19
+
20
+ from sqlalchemy import (
21
+ BigInteger,
22
+ Boolean,
23
+ Column,
24
+ DateTime,
25
+ Enum,
26
+ Float,
27
+ Integer,
28
+ String,
29
+ Text,
30
+ VARCHAR,
31
+ ForeignKey,
32
+ TEXT,
33
+ UniqueConstraint,
34
+ )
35
+ from sqlalchemy import inspect, func, text
36
+ from sqlalchemy.types import JSON
37
+
38
+ from sqlalchemy.orm import declarative_base, relationship, backref
39
+
40
+ from sqlalchemy_utils.types.uuid import UUIDType
41
+
42
+ from ccai.pychestrate.core.storage.defaults import utc_now
43
+
44
+ class SerializableModel:
45
+ """
46
+ Mixin style class that adds serialization to data model objects.
47
+ """
48
+
49
+ def to_dict(self):
50
+ """
51
+ Iterates the formal data attributes of a model and outputs a dictionary
52
+ with the data based on the model.
53
+ """
54
+ dval = {}
55
+
56
+ model = type(self)
57
+ mapper = inspect(model)
58
+ for col in mapper.attrs:
59
+ col_key = col.key
60
+ dval[col_key] = str(getattr(self, col_key))
61
+
62
+ return dval
63
+
64
+
65
+ def to_json(self, indent=4):
66
+ """
67
+ Iterates the formal data attributes of a model and creates a dictionary
68
+ with the data based on the model, then converts the dictionary to a
69
+ JSON string.
70
+ """
71
+ model_dict = self.to_dict()
72
+ json_str = json.dumps(model_dict, indent=indent)
73
+ return json_str
74
+
75
+
76
+ PychestrateModelBase = declarative_base()
77
+
78
+
79
+ class AccountStatus(str, enum.Enum):
80
+ """
81
+ Enum-like class to represent the status of a user or enterprise account.
82
+ """
83
+ ACTIVE = 'active'
84
+ INACTIVE = 'inactive'
85
+ SUSPENDED = 'suspended'
86
+ DELETED = 'deleted'
87
+
88
+ @classmethod
89
+ def is_valid(cls, status):
90
+ return status in (cls.ACTIVE, cls.INACTIVE, cls.SUSPENDED, cls.DELETED)
91
+
92
+
93
+ class AuthenticationType(str, enum.Enum):
94
+ """
95
+ Enum-like class to represent the type of authentications that should be used to authenticate the user.
96
+ """
97
+ INTERNAL = 'internal'
98
+ OAUTH = 'oauth2'
99
+
100
+
101
+ class RepositoryType(enum.Enum):
102
+ """Enumeration describing the primary content maintained by a repository."""
103
+
104
+ CODE = "code"
105
+ DOCUMENTATION = "documentation"
106
+
107
+
108
+ class RepositoryScope(enum.Enum):
109
+ """Enumeration describing the scope of artifacts provided by a repository."""
110
+
111
+ PRODUCT = "product"
112
+ LOCAL = "local"
113
+
114
+
115
+ class WorkQueueJobType(enum.Enum):
116
+ """
117
+ An enumeration that indicates the JobType of a WorkQueue item. This indicates if a
118
+ work item is a global work item available for execution on any qualified work site or
119
+ if it is a local work item which is meant to execute locally.
120
+ """
121
+ Local = 1
122
+ Global = 2
123
+
124
+
125
+ class WorkQueueState(enum.Enum):
126
+ """
127
+ An enumeration that indicates the JobType of a WorkQueue item. This indicates if a
128
+ work item is a global work item available for execution on any qualified work site or
129
+ if it is a local work item which is meant to execute locally.
130
+ """
131
+ Enabled = 1
132
+ Disabled = 2
133
+
134
+
135
+ class WorkPacketStatus(enum.Enum):
136
+ """Enumeration of lifecycle states for a :class:`WorkPacket`."""
137
+
138
+ QUEUED = "QUEUED"
139
+ RUNNING = "RUNNING"
140
+ SUCCEEDED = "SUCCEEDED"
141
+ FAILED = "FAILED"
142
+ CANCELED = "CANCELED"
143
+ BLOCKED = "BLOCKED"
144
+
145
+
146
+ class BuildJobTemplate(PychestrateModelBase, SerializableModel):
147
+ """
148
+ A data model that stores global build job templates.
149
+ """
150
+
151
+ __tablename__ = "buildjob_template"
152
+
153
+ id = Column('bjtmp_id', BigInteger, primary_key=True, autoincrement=True)
154
+ name = Column('bjtmp_name', VARCHAR(128), nullable=False, unique=True)
155
+ description = Column('bjtmp_description', Text, nullable=True)
156
+ specification = Column('bjtmp_specification', JSON, nullable=False)
157
+
158
+
159
+ class BuildTemplate(PychestrateModelBase, SerializableModel):
160
+ """
161
+ A data model that stores global build templates.
162
+ """
163
+
164
+ __tablename__ = "build_template"
165
+
166
+ id = Column('btmp_id', BigInteger, primary_key=True, autoincrement=True)
167
+ name = Column('btmp_name', VARCHAR(128), nullable=False, unique=True)
168
+ description = Column('btmp_description', Text, nullable=True)
169
+ specification = Column('btmp_specification', JSON, nullable=False)
170
+
171
+
172
+ class TestJobTemplate(PychestrateModelBase, SerializableModel):
173
+ """
174
+ A data model that stores global test job templates.
175
+ """
176
+
177
+ __tablename__ = "testjob_template"
178
+
179
+ id = Column('tjtmp_id', BigInteger, primary_key=True, autoincrement=True)
180
+ name = Column('tjtmp_name', VARCHAR(128), nullable=False, unique=True)
181
+ description = Column('tjtmp_description', Text, nullable=True)
182
+ specification = Column('tjtmp_specification', JSON, nullable=False)
183
+
184
+
185
+
186
+ class Product(PychestrateModelBase, SerializableModel):
187
+ """A data model that represents a product composed from multiple components."""
188
+
189
+ __tablename__ = "product"
190
+
191
+ id = Column("prod_id", BigInteger, primary_key=True, autoincrement=True)
192
+ name = Column("prod_name", VARCHAR(128), nullable=False, unique=True)
193
+ description = Column("prod_description", Text, nullable=True)
194
+
195
+ components = relationship(
196
+ "ProductComponent",
197
+ back_populates="product",
198
+ cascade="all, delete-orphan",
199
+ lazy="selectin",
200
+ )
201
+
202
+ product_repositories = relationship(
203
+ "ProductToRepository",
204
+ back_populates="product",
205
+ cascade="all, delete-orphan",
206
+ lazy="selectin",
207
+ )
208
+
209
+ releases = relationship(
210
+ "Release",
211
+ back_populates="product",
212
+ cascade="all, delete-orphan",
213
+ lazy="selectin",
214
+ )
215
+
216
+
217
+ class ProductComponent(PychestrateModelBase, SerializableModel):
218
+ """Represents a component of a product that maps to a source repository."""
219
+
220
+ __tablename__ = "product_component"
221
+
222
+ __table_args__ = (
223
+ UniqueConstraint("prod_id", "comp_name", name="uq_component_product_name"),
224
+ )
225
+
226
+ id = Column("comp_id", BigInteger, primary_key=True, autoincrement=True)
227
+ name = Column("comp_name", VARCHAR(128), nullable=False)
228
+ description = Column("comp_description", Text, nullable=True)
229
+
230
+ product_id = Column(
231
+ "prod_id", BigInteger, ForeignKey("product.prod_id"), nullable=False, index=True
232
+ )
233
+ repository_id = Column(
234
+ "repo_id", BigInteger, ForeignKey("repository.repo_id"), nullable=False, index=True
235
+ )
236
+
237
+ product = relationship("Product", back_populates="components", lazy="selectin")
238
+ repository = relationship("Repository", back_populates="components", lazy="selectin")
239
+
240
+
241
+ class ProductToRepository(PychestrateModelBase, SerializableModel):
242
+ """Associates product level repositories such as documentation with a product."""
243
+
244
+ __tablename__ = "product_to_repository"
245
+
246
+ __table_args__ = (
247
+ UniqueConstraint("prod_id", "repo_id", name="uq_product_repository"),
248
+ )
249
+
250
+ id = Column("ptor_id", BigInteger, primary_key=True, autoincrement=True)
251
+ product_id = Column(
252
+ "prod_id", BigInteger, ForeignKey("product.prod_id"), nullable=False, index=True
253
+ )
254
+ repository_id = Column(
255
+ "repo_id", BigInteger, ForeignKey("repository.repo_id"), nullable=False, index=True
256
+ )
257
+
258
+ product = relationship("Product", back_populates="product_repositories", lazy="selectin")
259
+ repository = relationship("Repository", back_populates="product_links", lazy="selectin")
260
+
261
+
262
+ class ReleaseTrain(PychestrateModelBase, SerializableModel):
263
+ """Represents the symbolic branch structure used to orchestrate a release train."""
264
+
265
+ __tablename__ = "release_train"
266
+
267
+ id = Column("rtrain_id", BigInteger, primary_key=True, autoincrement=True)
268
+ name = Column("rtrain_name", VARCHAR(128), nullable=False, unique=True)
269
+ description = Column("rtrain_description", Text, nullable=True)
270
+
271
+ branches = relationship(
272
+ "ReleaseTrainBranch",
273
+ back_populates="train",
274
+ cascade="all, delete-orphan",
275
+ lazy="selectin",
276
+ )
277
+
278
+ releases = relationship(
279
+ "Release",
280
+ back_populates="release_train",
281
+ lazy="selectin",
282
+ )
283
+
284
+
285
+ class ReleaseTrainBranchType(str, enum.Enum):
286
+ """Enumerates the logical role a symbolic branch plays within a release train."""
287
+
288
+ TEAM = "team"
289
+ INTEGRATION = "integration"
290
+ MAINLINE = "mainline"
291
+ SUPPORT = "support"
292
+ OTHER = "other"
293
+
294
+
295
+ class ReleaseTrainBranch(PychestrateModelBase, SerializableModel):
296
+ """Symbolic branches that define the flow of a release train."""
297
+
298
+ __tablename__ = "release_train_branch"
299
+
300
+ __table_args__ = (
301
+ UniqueConstraint("rtrain_id", "rtbranch_name", name="uq_release_train_branch"),
302
+ )
303
+
304
+ id = Column("rtbranch_id", BigInteger, primary_key=True, autoincrement=True)
305
+ name = Column("rtbranch_name", VARCHAR(128), nullable=False)
306
+ description = Column("rtbranch_description", Text, nullable=True)
307
+ branch_type = Column(
308
+ "rtbranch_type",
309
+ Enum(ReleaseTrainBranchType, name="release_train_branch_type", native_enum=False),
310
+ nullable=False,
311
+ default=ReleaseTrainBranchType.OTHER,
312
+ )
313
+ display_order = Column("rtbranch_display_order", Integer, nullable=False, default=0)
314
+ is_terminal = Column("rtbranch_is_terminal", Boolean, nullable=False, default=False)
315
+ metadata_payload = Column("rtbranch_metadata", JSON, nullable=True)
316
+
317
+ release_train_id = Column(
318
+ "rtrain_id", BigInteger, ForeignKey("release_train.rtrain_id"), nullable=False, index=True
319
+ )
320
+ parent_id = Column(
321
+ "parent_rtbranch_id",
322
+ BigInteger,
323
+ ForeignKey("release_train_branch.rtbranch_id"),
324
+ nullable=True,
325
+ index=True,
326
+ )
327
+
328
+ train = relationship("ReleaseTrain", back_populates="branches", lazy="selectin")
329
+ parent = relationship(
330
+ "ReleaseTrainBranch",
331
+ remote_side=lambda: [ReleaseTrainBranch.id],
332
+ backref=backref(
333
+ "children",
334
+ lazy="selectin",
335
+ order_by="ReleaseTrainBranch.display_order",
336
+ ),
337
+ lazy="selectin",
338
+ )
339
+
340
+ branch_assignments = relationship(
341
+ "ReleaseBranchAssignment",
342
+ back_populates="symbolic_branch",
343
+ cascade="all, delete-orphan",
344
+ lazy="selectin",
345
+ )
346
+
347
+
348
+ class Release(PychestrateModelBase, SerializableModel):
349
+ """Represents a release of a product built from a release train."""
350
+
351
+ __tablename__ = "release"
352
+
353
+ __table_args__ = (
354
+ UniqueConstraint("prod_id", "release_name", name="uq_release_product_name"),
355
+ )
356
+
357
+ id = Column("release_id", BigInteger, primary_key=True, autoincrement=True)
358
+ name = Column("release_name", VARCHAR(128), nullable=False)
359
+ description = Column("release_description", Text, nullable=True)
360
+ documentation_url = Column("release_documentation_url", VARCHAR(2048), nullable=True)
361
+ start_date = Column("release_start_date", DateTime(timezone=True), nullable=True)
362
+ release_date = Column("release_release_date", DateTime(timezone=True), nullable=True)
363
+
364
+ product_id = Column(
365
+ "prod_id", BigInteger, ForeignKey("product.prod_id"), nullable=False, index=True
366
+ )
367
+ release_train_id = Column(
368
+ "rtrain_id", BigInteger, ForeignKey("release_train.rtrain_id"), nullable=True, index=True
369
+ )
370
+
371
+ product = relationship("Product", back_populates="releases", lazy="selectin")
372
+ release_train = relationship("ReleaseTrain", back_populates="releases", lazy="selectin")
373
+
374
+ branch_assignments = relationship(
375
+ "ReleaseBranchAssignment",
376
+ back_populates="release",
377
+ cascade="all, delete-orphan",
378
+ lazy="selectin",
379
+ )
380
+
381
+
382
+ class ReleaseBranchAssignment(PychestrateModelBase, SerializableModel):
383
+ """Maps a symbolic release train branch to a literal branch name for a release."""
384
+
385
+ __tablename__ = "release_branch_assignment"
386
+
387
+ __table_args__ = (
388
+ UniqueConstraint(
389
+ "release_id",
390
+ "rtbranch_id",
391
+ name="uq_release_branch_assignment",
392
+ ),
393
+ )
394
+
395
+ id = Column("rba_id", BigInteger, primary_key=True, autoincrement=True)
396
+ literal_branch_name = Column("rba_literal_branch_name", VARCHAR(256), nullable=False)
397
+
398
+ release_id = Column(
399
+ "release_id", BigInteger, ForeignKey("release.release_id"), nullable=False, index=True
400
+ )
401
+ release_train_branch_id = Column(
402
+ "rtbranch_id",
403
+ BigInteger,
404
+ ForeignKey("release_train_branch.rtbranch_id"),
405
+ nullable=False,
406
+ index=True,
407
+ )
408
+
409
+ release = relationship("Release", back_populates="branch_assignments", lazy="selectin")
410
+ symbolic_branch = relationship("ReleaseTrainBranch", back_populates="branch_assignments", lazy="selectin")
411
+
412
+
413
+ class Repository(PychestrateModelBase, SerializableModel):
414
+ """
415
+ A data modelf for storing repository information.
416
+ """
417
+
418
+ __tablename__ = "repository"
419
+
420
+ __table_args__ = (UniqueConstraint('repo_name', 'repo_repository', name='uq_repository_name_url'),)
421
+
422
+ id = Column('repo_id', BigInteger, primary_key=True, autoincrement=True)
423
+ name = Column('repo_name', VARCHAR(128), nullable=False)
424
+ url = Column('repo_repository', VARCHAR(2048), nullable=False, unique=True)
425
+ repotype = Column('repo_type', Enum(RepositoryType, name='repository_type', native_enum=False), nullable=False)
426
+ scope = Column('repo_scope', Enum(RepositoryScope, name='repository_scope', native_enum=False), nullable=False)
427
+ repoconf = Column('repo_conf', JSON, nullable=False)
428
+ product_links = relationship('ProductToRepository', back_populates='repository', cascade='all, delete-orphan', lazy='selectin')
429
+ components = relationship('ProductComponent', back_populates='repository', lazy='selectin')
430
+ branches = relationship('Branch', back_populates='repository', cascade='all, delete-orphan', lazy='selectin')
431
+
432
+
433
+ class Branch(PychestrateModelBase, SerializableModel):
434
+ """
435
+ A data model for storing source branch information.
436
+ """
437
+
438
+ __tablename__ = "branch"
439
+
440
+ __table_args__ = (UniqueConstraint('repo_id', 'branch_name', name='uq_branch_repo_name'),)
441
+
442
+ id = Column('branch_id', BigInteger, primary_key=True, autoincrement=True)
443
+ name = Column('branch_name', VARCHAR(128), nullable=False)
444
+ owner = Column('branch_owner', VARCHAR(128), nullable=False)
445
+
446
+ repo_id = Column('repo_id', BigInteger, ForeignKey('repository.repo_id'), nullable=False, index=True)
447
+
448
+ repository = relationship('Repository', back_populates='branches', lazy='selectin')
449
+
450
+ build_jobs = relationship('BuildJob', back_populates='branch', cascade='all, delete-orphan', lazy='selectin')
451
+ build_results = relationship('BuildResult', back_populates='branch', cascade='all, delete-orphan', lazy='selectin')
452
+
453
+
454
+ class BuildJob(PychestrateModelBase, SerializableModel):
455
+ """
456
+ A data model for storing build job information.
457
+ """
458
+
459
+ __tablename__ = "build_job"
460
+
461
+ id = Column('bjob_id', BigInteger, primary_key=True, autoincrement=True)
462
+ name = Column('bjob_name', VARCHAR(128), nullable=False)
463
+ start = Column('bjob_start', DateTime(timezone=True), nullable=False, server_default=func.now())
464
+ stop = Column('bjob_stop', DateTime(timezone=True), nullable=True)
465
+ change = Column('bjob_change', VARCHAR(128), nullable=False)
466
+ status = Column('bjob_status', Enum(WorkPacketStatus, name='build_job_status', native_enum=False), nullable=False, default=WorkPacketStatus.QUEUED)
467
+
468
+ branch_id = Column('branch_id', BigInteger, ForeignKey('branch.branch_id'), nullable=False, index=True)
469
+
470
+ branch = relationship('Branch', back_populates='build_jobs', lazy='selectin')
471
+ result = relationship('BuildResult', back_populates='job', uselist=False, cascade='all, delete-orphan', lazy='selectin')
472
+
473
+
474
+ class BuildResult(PychestrateModelBase, SerializableModel):
475
+ """
476
+ A data model for storing build information.
477
+ """
478
+
479
+ __tablename__ = "build_result"
480
+
481
+ id = Column('build_id', BigInteger, primary_key=True, autoincrement=True)
482
+ name = Column('build_name', VARCHAR(128), nullable=False)
483
+ start = Column('build_start', DateTime(timezone=True), nullable=False, server_default=func.now())
484
+ stop = Column('build_stop', DateTime(timezone=True), nullable=True)
485
+ change = Column('build_change', VARCHAR(128), nullable=False)
486
+
487
+ branch_id = Column('branch_id', BigInteger, ForeignKey('branch.branch_id'), nullable=False, index=True)
488
+ job_id = Column('bjob_id', BigInteger, ForeignKey('build_job.bjob_id'), nullable=True, index=True)
489
+
490
+ branch = relationship('Branch', back_populates='build_results', lazy='selectin')
491
+ job = relationship('BuildJob', back_populates='result', lazy='selectin')
492
+
493
+ byproducts = relationship('BuildByProduct', back_populates='build', cascade='all, delete-orphan', lazy='selectin')
494
+
495
+
496
+ class BuildByProduct(PychestrateModelBase, SerializableModel):
497
+ """
498
+ A data mode for linking build byproducts to build
499
+ """
500
+ __tablename__ = "build_byproduct"
501
+
502
+ id = Column('bbp_id', BigInteger, primary_key=True, autoincrement=True)
503
+ product = Column('bbp_product', VARCHAR(128), nullable=False)
504
+ arch = Column('bbp_arch', VARCHAR(128), nullable=False)
505
+ buildno = Column('bbp_buildno', VARCHAR(128), nullable=False)
506
+ flavor = Column('bbp_flavor', VARCHAR(128), nullable=False)
507
+ result_url = Column('bbp_result_url', VARCHAR(2048), nullable=False)
508
+
509
+ build_id = Column('build_id', BigInteger, ForeignKey('build_result.build_id'), nullable=False, index=True)
510
+
511
+ build = relationship('BuildResult', back_populates='byproducts', lazy='selectin')
512
+
513
+
514
+ class User(PychestrateModelBase, SerializableModel):
515
+ """
516
+ A data model for the minimal amount of User information to store
517
+ for relating automation data to an enterprise user.
518
+ """
519
+
520
+ __tablename__ = 'user'
521
+
522
+ id = Column('user_id', UUIDType, primary_key=True, default=uuid.uuid4, server_default=text('gen_random_uuid()'))
523
+ first_name = Column('user_firstName', VARCHAR(40), nullable=False)
524
+ last_name = Column('user_lastName', VARCHAR(40), nullable=False)
525
+ email = Column('user_email', VARCHAR(128), nullable=False, unique=True)
526
+ login = Column('user_login', VARCHAR(128), nullable=False, unique=True)
527
+ ownertag = Column('user_ownertag', VARCHAR(40), nullable=True)
528
+
529
+ work_packets = relationship('WorkPacket', back_populates='user', lazy='selectin')
530
+
531
+ test_runs = relationship('TestJobRun', back_populates='user', lazy='selectin')
532
+
533
+ feature_groups = relationship('FeatureGroupMembership', back_populates='user', lazy='selectin', cascade='all, delete-orphan')
534
+
535
+
536
+ class WorkQueue(PychestrateModelBase, SerializableModel):
537
+ """
538
+ A data model for a
539
+ """
540
+ __tablename__ = 'work_queue'
541
+
542
+ id = Column('queue_id', UUIDType, primary_key=True, nullable=False, default=uuid.uuid4, server_default=text('gen_random_uuid()'))
543
+ name = Column('queue_name', String(128), nullable=False, unique=True)
544
+ qtype = Column('queue_type', String(128), nullable=False)
545
+ description = Column('queue_desc', TEXT, nullable=False)
546
+ state = Column('queue_state', Enum(WorkQueueState, name='work_queue_state', native_enum=False), nullable=False)
547
+
548
+ packets = relationship('WorkPacket', back_populates='queue', cascade='all, delete-orphan', lazy='selectin')
549
+
550
+
551
+ class WorkPacket(PychestrateModelBase, SerializableModel):
552
+ """
553
+ A data model for a WorkPacket(s) and the work items that are part of a work queue.
554
+ """
555
+ __tablename__ = 'work_packet'
556
+
557
+ id = Column('wkpack_id', BigInteger, primary_key=True, autoincrement=True)
558
+
559
+ jtype = Column('wkpk_jtype', Enum(WorkQueueJobType, name='work_queue_job_type', native_enum=False), nullable=False)
560
+ title = Column('wkpk_title', String(1024), nullable=False)
561
+ description = Column('wkpk_description', Text, nullable=False)
562
+ branch = Column('wkpk_branch', String(1024), nullable=True)
563
+ build = Column('wkpk_build', String(1024), nullable=True)
564
+ flavor = Column('wkpk_flavor', String(1024), nullable=True)
565
+ ownertag = Column('wkpk_ownertag', VARCHAR(40), nullable=True)
566
+ added = Column('wkpk_added', DateTime(timezone=True), nullable=False, server_default=func.now())
567
+ start = Column('wkpk_start', DateTime(timezone=True), nullable=True)
568
+ stop = Column('wkpk_stop', DateTime(timezone=True), nullable=True)
569
+ progress = Column('wkpk_progress', Float, default=0.0)
570
+ status = Column('wkpk_status', Enum(WorkPacketStatus, name='work_packet_status', native_enum=False), nullable=False, index=True)
571
+ packet = Column('wkpk_packet', TEXT, nullable=False)
572
+
573
+ queue_id = Column('queue_id', UUIDType, ForeignKey('work_queue.queue_id'), nullable=False, index=True)
574
+ user_id = Column('user_id', UUIDType, ForeignKey('user.user_id'), nullable=False, index=True)
575
+
576
+ queue = relationship('WorkQueue', back_populates='packets', lazy='selectin')
577
+
578
+ user = relationship('User', back_populates='work_packets', lazy='selectin')
579
+
580
+
581
+ class FeatureGroup(PychestrateModelBase, SerializableModel):
582
+ """
583
+ A data model for storing information about feature groups.
584
+ """
585
+ __tablename__ = 'feature_group'
586
+
587
+ id = Column('fg_id', BigInteger, primary_key=True, autoincrement=True)
588
+ name = Column('fg_name', VARCHAR(256), nullable=False)
589
+ description = Column('fg_description', TEXT, nullable=False)
590
+
591
+ test_jobs = relationship('TestJob', back_populates='owner_feature_group', lazy='selectin')
592
+
593
+ memberships = relationship('FeatureGroupMembership', back_populates='feature_group', cascade='all, delete-orphan', lazy='selectin')
594
+
595
+ test_runs = relationship('TestJobRun', back_populates='feature_group', lazy='selectin')
596
+
597
+
598
+ class TestJob(PychestrateModelBase, SerializableModel):
599
+ """
600
+ A data model for storing information about test jobs and
601
+ user and feature group associations with a given test job.
602
+ """
603
+ __tablename__ = 'test_job'
604
+
605
+ id = Column('tj_id', UUIDType, primary_key=True, default=uuid.uuid4, server_default=text('gen_random_uuid()'))
606
+ title = Column('tj_title', VARCHAR(1024), nullable=False)
607
+ description = Column('tj_description', Text, nullable=False)
608
+
609
+ owner_fg_id = Column('owner_fg_id', BigInteger, ForeignKey('feature_group.fg_id'), nullable=True, index=True)
610
+
611
+ owner_feature_group = relationship('FeatureGroup', back_populates='test_jobs', lazy='selectin')
612
+
613
+ runs = relationship('TestJobRun', back_populates='job', cascade='all, delete-orphan', lazy='selectin')
614
+
615
+ result_containers = relationship('TestResultContainer', back_populates='test_job', cascade='all, delete-orphan', lazy='selectin')
616
+
617
+
618
+ class TestJobRun(PychestrateModelBase, SerializableModel):
619
+ """
620
+ A data model for a TestJob run.
621
+ """
622
+
623
+ __tablename__ = 'test_job_run'
624
+
625
+ instance = Column('tjr_instance', UUIDType, nullable=False, primary_key=True, default=uuid.uuid4, server_default=text('gen_random_uuid()'))
626
+ branch = Column('tjr_branch', VARCHAR(1024), nullable=True)
627
+ build = Column('tjr_build', VARCHAR(1024), nullable=True)
628
+ flavor = Column('tjr_flavor', VARCHAR(1024), nullable=True)
629
+ ownertag = Column('wkpk_ownertag', UUIDType, nullable=True)
630
+ start = Column('tjr_start', DateTime(timezone=True), nullable=False, server_default=func.now(), index=True)
631
+ stop = Column('tjr_stop', DateTime(timezone=True), nullable=True)
632
+ detail = Column('tjr_detail', JSON, nullable=True)
633
+
634
+ job_id = Column('job_id', UUIDType, ForeignKey('test_job.tj_id'), nullable=False, index=True)
635
+ user_id = Column('user_id', UUIDType, ForeignKey('user.user_id'), nullable=False, index=True)
636
+
637
+ fg_id = Column('fg_id', BigInteger, ForeignKey('feature_group.fg_id'), nullable=True, index=True)
638
+
639
+ job = relationship('TestJob', back_populates='runs', lazy='selectin')
640
+
641
+ user = relationship('User', back_populates='test_runs', lazy='selectin')
642
+
643
+ feature_group = relationship('FeatureGroup', back_populates='test_runs', lazy='selectin')
644
+
645
+ results = relationship('TestResult', back_populates='test_run', cascade='all, delete-orphan', lazy='selectin')
646
+
647
+
648
+ class TestResult(PychestrateModelBase, SerializableModel):
649
+ """
650
+ A data model for a TestResult node that is part of a test result tree.
651
+ """
652
+ __tablename__ = 'test_result'
653
+
654
+ instance = Column('tstr_instance', UUIDType, nullable=False, primary_key=True, default=uuid.uuid4, server_default=text('gen_random_uuid()'))
655
+ name = Column('tstr_name', VARCHAR(1024), nullable=False)
656
+ monikers = Column('tstr_monikers', JSON, nullable=True)
657
+ pivots = Column('tstr_pivots', JSON, nullable=True)
658
+ rtype = Column('tstr_rtype', String(50), nullable=False)
659
+ result = Column('tstr_result', String(50), nullable=False)
660
+ start = Column('tstr_start', DateTime(timezone=True), nullable=False, server_default=func.now())
661
+ stop = Column('tstr_stop', DateTime(timezone=True), nullable=True)
662
+ detail = Column('tstr_detail', JSON, nullable=True)
663
+
664
+ testrun = Column('tjr_instance', UUIDType, ForeignKey('test_job_run.tjr_instance'), nullable=True, index=True)
665
+
666
+ test_run = relationship('TestJobRun', back_populates='results', lazy='selectin')
667
+
668
+
669
+ class TestResultContainer(PychestrateModelBase, SerializableModel):
670
+ """
671
+ A data model for a TestResultContainer node that is part of a test result tree. The
672
+ TestResultContainer node serves as a parent and container for individual result based
673
+ nodes.
674
+ """
675
+ __tablename__ = 'test_result_container'
676
+
677
+ id = Column('tstrcont_id', BigInteger, primary_key=True, autoincrement=True)
678
+ name = Column('tstrcont_name', VARCHAR(1024), nullable=False)
679
+ instance = Column('tstrcont_instance', UUIDType, nullable=False, default=uuid.uuid4, server_default=text('gen_random_uuid()'))
680
+ parent = Column('tstrcont_parent', UUIDType, nullable=True)
681
+ rtype = Column('tstrcont_rtype', String(50), nullable=False)
682
+
683
+ testjob_id = Column('tj_id', UUIDType, ForeignKey('test_job.tj_id'), index=True)
684
+
685
+ test_job = relationship('TestJob', back_populates='result_containers', lazy='selectin')
686
+
687
+
688
+ class FeatureGroupMembership(PychestrateModelBase, SerializableModel):
689
+ """
690
+ A data model for associating FeatureGroup(s) to Users(s)
691
+ """
692
+ __tablename__ = "feature_group_membership"
693
+
694
+ fg_id = Column('fg_id', BigInteger, ForeignKey('feature_group.fg_id'), primary_key=True, index=True)
695
+ user_id = Column('user_id', UUIDType, ForeignKey('user.user_id'), primary_key=True, index=True)
696
+
697
+ join_date = Column('fgm_join_date', DateTime(timezone=True), nullable=False, server_default=func.now())
698
+ departure_date = Column('fgm_departure_date', DateTime(timezone=True), nullable=True)
699
+
700
+ feature_group = relationship('FeatureGroup', back_populates='memberships', lazy='selectin')
701
+
702
+ user = relationship('User', back_populates='feature_groups', lazy='selectin')
703
+
704
+
705
+ class OauthUser(PychestrateModelBase, SerializableModel):
706
+ """Represents a user authenticated through an OAuth provider."""
707
+
708
+ __tablename__ = "users"
709
+
710
+ id = Column('user_id', VARCHAR(40), primary_key=True)
711
+
712
+ provider = Column('user_provider', VARCHAR(50), nullable=True, doc="OAuth provider name (google, github, gitlab, microsoft, apple)")
713
+ provider_ref_id = Column('user_provider_ref_id', VARCHAR(255), nullable=False, index=True, doc="Unique identifier from the OAuth provider" \
714
+ " (sub or id) for the user account")
715
+ firstname = Column('user_firstname', VARCHAR(255), nullable=False, doc="First name of the user, if provided")
716
+ lastname = Column('user_lastname', VARCHAR(255), nullable=False, doc="Last name of the user, if provided")
717
+ display_name = Column('user_display_name', VARCHAR(255), nullable=True, doc="Display name of the user")
718
+ username = Column('user_username', VARCHAR(255), nullable=False, doc="Username of the user, if provided")
719
+ email = Column('user_email', VARCHAR(255), nullable=False, doc="Email address associated with the account")
720
+ avatar_uri = Column('user_avatar_uri', VARCHAR(1024), nullable=True, doc="URI of the user's avatar or profile picture")
721
+ status = Column('user_status', VARCHAR(20), nullable=False, default='active', doc="Status of the user account (e.g., 'active', 'inactive')")
722
+ roles = Column('user_roles', VARCHAR(1024), nullable=True, doc="Comma-separated list of roles assigned to the user (e.g., 'admin,user')")
723
+ authtype = Column('user_authtype', VARCHAR(20), nullable=False, default='consumer', doc="Type of user (e.g., 'internal', 'consumer')")
724
+ created_at = Column('user_created_at', DateTime, default=utc_now, nullable=False, doc="Timestamp when the user record was created")
725
+ updated_at = Column('user_updated_at', DateTime, default=utc_now, onupdate=utc_now, nullable=False, doc="Timestamp when the user record was last updated")