Skip to content

models

persistence.models

BatchSimulationModel

Bases: SimulationModel

Batch simulation model

Source code in yaptide/persistence/models.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
class BatchSimulationModel(SimulationModel):
    """Batch simulation model"""

    __tablename__ = 'BatchSimulation'
    id: Column[int] = db.Column(db.Integer, db.ForeignKey('Simulation.id', ondelete="CASCADE"), primary_key=True)
    cluster_id: Column[int] = db.Column(db.Integer, db.ForeignKey('Cluster.id'), nullable=False, doc="Cluster ID")
    job_dir: Column[str] = db.Column(db.String, nullable=True, doc="Simulation folder name")
    array_id: Column[int] = db.Column(db.Integer, nullable=True, doc="Batch array jon ID")
    collect_id: Column[int] = db.Column(db.Integer, nullable=True, doc="Batch collect job ID")

    __mapper_args__ = {"polymorphic_identity": PlatformType.BATCH.value, "polymorphic_load": "inline"}

    def update_state(self, update_dict):
        """Used to update fields in BatchSimulation. Returns boolean value if commit to database is reuqired"""
        db_commit_required = super().update_state(update_dict)
        if "job_dir" in update_dict and self.job_dir != update_dict["job_dir"]:
            self.job_dir = update_dict["job_dir"]
            db_commit_required = True
        if "array_id" in update_dict and self.array_id != update_dict["array_id"]:
            self.array_id = update_dict["array_id"]
            db_commit_required = True
        if "collect_id" in update_dict and self.collect_id != update_dict["collect_id"]:
            self.collect_id = update_dict["collect_id"]
            db_commit_required = True
        return db_commit_required

__mapper_args__ class-attribute instance-attribute

__mapper_args__ = {
    "polymorphic_identity": value,
    "polymorphic_load": "inline",
}

__tablename__ class-attribute instance-attribute

__tablename__ = 'BatchSimulation'

array_id class-attribute instance-attribute

array_id = Column(
    Integer, nullable=True, doc="Batch array jon ID"
)

cluster_id class-attribute instance-attribute

cluster_id = Column(
    Integer,
    ForeignKey("Cluster.id"),
    nullable=False,
    doc="Cluster ID",
)

collect_id class-attribute instance-attribute

collect_id = Column(
    Integer, nullable=True, doc="Batch collect job ID"
)

id class-attribute instance-attribute

id = Column(
    Integer,
    ForeignKey("Simulation.id", ondelete="CASCADE"),
    primary_key=True,
)

job_dir class-attribute instance-attribute

job_dir = Column(
    String, nullable=True, doc="Simulation folder name"
)

update_state

update_state(update_dict)

Used to update fields in BatchSimulation. Returns boolean value if commit to database is reuqired

Source code in yaptide/persistence/models.py
151
152
153
154
155
156
157
158
159
160
161
162
163
def update_state(self, update_dict):
    """Used to update fields in BatchSimulation. Returns boolean value if commit to database is reuqired"""
    db_commit_required = super().update_state(update_dict)
    if "job_dir" in update_dict and self.job_dir != update_dict["job_dir"]:
        self.job_dir = update_dict["job_dir"]
        db_commit_required = True
    if "array_id" in update_dict and self.array_id != update_dict["array_id"]:
        self.array_id = update_dict["array_id"]
        db_commit_required = True
    if "collect_id" in update_dict and self.collect_id != update_dict["collect_id"]:
        self.collect_id = update_dict["collect_id"]
        db_commit_required = True
    return db_commit_required

BatchTaskModel

Bases: TaskModel

Batch task model

Source code in yaptide/persistence/models.py
282
283
284
285
286
287
288
class BatchTaskModel(TaskModel):
    """Batch task model"""

    __tablename__ = 'BatchTask'
    id: Column[int] = db.Column(db.Integer, db.ForeignKey('Task.id', ondelete="CASCADE"), primary_key=True)

    __mapper_args__ = {"polymorphic_identity": PlatformType.BATCH.value, "polymorphic_load": "inline"}

__mapper_args__ class-attribute instance-attribute

__mapper_args__ = {
    "polymorphic_identity": value,
    "polymorphic_load": "inline",
}

__tablename__ class-attribute instance-attribute

__tablename__ = 'BatchTask'

id class-attribute instance-attribute

id = Column(
    Integer,
    ForeignKey("Task.id", ondelete="CASCADE"),
    primary_key=True,
)

CelerySimulationModel

Bases: SimulationModel

Celery simulation model

Source code in yaptide/persistence/models.py
129
130
131
132
133
134
135
136
class CelerySimulationModel(SimulationModel):
    """Celery simulation model"""

    __tablename__ = 'CelerySimulation'
    id: Column[int] = db.Column(db.Integer, db.ForeignKey('Simulation.id', ondelete="CASCADE"), primary_key=True)
    merge_id: Column[str] = db.Column(db.String, nullable=True, doc="Celery collect job ID")

    __mapper_args__ = {"polymorphic_identity": PlatformType.DIRECT.value, "polymorphic_load": "inline"}

__mapper_args__ class-attribute instance-attribute

__mapper_args__ = {
    "polymorphic_identity": value,
    "polymorphic_load": "inline",
}

__tablename__ class-attribute instance-attribute

__tablename__ = 'CelerySimulation'

id class-attribute instance-attribute

id = Column(
    Integer,
    ForeignKey("Simulation.id", ondelete="CASCADE"),
    primary_key=True,
)

merge_id class-attribute instance-attribute

merge_id = Column(
    String, nullable=True, doc="Celery collect job ID"
)

CeleryTaskModel

Bases: TaskModel

Celery task model

Source code in yaptide/persistence/models.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
class CeleryTaskModel(TaskModel):
    """Celery task model"""

    __tablename__ = 'CeleryTask'
    id: Column[int] = db.Column(db.Integer, db.ForeignKey('Task.id', ondelete="CASCADE"), primary_key=True)
    celery_id: Column[str] = db.Column(db.String, nullable=False, default="", doc="Celery task ID")

    def update_state(self, update_dict: dict):
        """Update method for CeleryTaskModel"""
        if "celery_id" in update_dict and self.celery_id != update_dict["celery_id"]:
            self.celery_id = update_dict["celery_id"]
        return super().update_state(update_dict)

    __mapper_args__ = {"polymorphic_identity": PlatformType.DIRECT.value, "polymorphic_load": "inline"}

__mapper_args__ class-attribute instance-attribute

__mapper_args__ = {
    "polymorphic_identity": value,
    "polymorphic_load": "inline",
}

__tablename__ class-attribute instance-attribute

__tablename__ = 'CeleryTask'

celery_id class-attribute instance-attribute

celery_id = Column(
    String, nullable=False, default="", doc="Celery task ID"
)

id class-attribute instance-attribute

id = Column(
    Integer,
    ForeignKey("Task.id", ondelete="CASCADE"),
    primary_key=True,
)

update_state

update_state(update_dict)

Update method for CeleryTaskModel

Source code in yaptide/persistence/models.py
273
274
275
276
277
def update_state(self, update_dict: dict):
    """Update method for CeleryTaskModel"""
    if "celery_id" in update_dict and self.celery_id != update_dict["celery_id"]:
        self.celery_id = update_dict["celery_id"]
    return super().update_state(update_dict)

ClusterModel

Bases: Model

Cluster info for specific user

Source code in yaptide/persistence/models.py
63
64
65
66
67
68
69
class ClusterModel(db.Model):
    """Cluster info for specific user"""

    __tablename__ = 'Cluster'
    id: Column[int] = db.Column(db.Integer, primary_key=True)
    cluster_name: Column[str] = db.Column(db.String, nullable=False)
    simulations = relationship("BatchSimulationModel")

__tablename__ class-attribute instance-attribute

__tablename__ = 'Cluster'

cluster_name class-attribute instance-attribute

cluster_name = Column(String, nullable=False)

id class-attribute instance-attribute

id = Column(Integer, primary_key=True)

simulations class-attribute instance-attribute

simulations = relationship('BatchSimulationModel')

EstimatorModel

Bases: Model

Simulation single estimator model

Source code in yaptide/persistence/models.py
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
class EstimatorModel(db.Model):
    """Simulation single estimator model"""

    __tablename__ = 'Estimator'
    id: Column[int] = db.Column(db.Integer, primary_key=True)
    simulation_id: Column[int] = db.Column(db.Integer,
                                           db.ForeignKey('Simulation.id', ondelete="CASCADE"),
                                           nullable=False)
    name: Column[str] = db.Column(db.String, nullable=False, doc="Estimator name")
    compressed_data: Column[bytes] = db.Column(db.LargeBinary, doc="Estimator metadata")
    pages = relationship("PageModel", cascade="delete")

    @property
    def data(self):
        return decompress(self.compressed_data)

    @data.setter
    def data(self, value):
        if value is not None:
            self.compressed_data = compress(value)

__tablename__ class-attribute instance-attribute

__tablename__ = 'Estimator'

compressed_data class-attribute instance-attribute

compressed_data = Column(
    LargeBinary, doc="Estimator metadata"
)

data property writable

data

id class-attribute instance-attribute

id = Column(Integer, primary_key=True)

name class-attribute instance-attribute

name = Column(String, nullable=False, doc='Estimator name')

pages class-attribute instance-attribute

pages = relationship('PageModel', cascade='delete')

simulation_id class-attribute instance-attribute

simulation_id = Column(
    Integer,
    ForeignKey("Simulation.id", ondelete="CASCADE"),
    nullable=False,
)

InputModel

Bases: Model

Simulation inputs model

Source code in yaptide/persistence/models.py
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
class InputModel(db.Model):
    """Simulation inputs model"""

    __tablename__ = 'Input'
    id: Column[int] = db.Column(db.Integer, primary_key=True)
    simulation_id: Column[int] = db.Column(db.Integer, db.ForeignKey('Simulation.id', ondelete="CASCADE"))
    compressed_data: Column[bytes] = db.Column(db.LargeBinary)

    @property
    def data(self):
        return decompress(self.compressed_data)

    @data.setter
    def data(self, value):
        if value is not None:
            self.compressed_data = compress(value)

__tablename__ class-attribute instance-attribute

__tablename__ = 'Input'

compressed_data class-attribute instance-attribute

compressed_data = Column(LargeBinary)

data property writable

data

id class-attribute instance-attribute

id = Column(Integer, primary_key=True)

simulation_id class-attribute instance-attribute

simulation_id = Column(
    Integer, ForeignKey("Simulation.id", ondelete="CASCADE")
)

KeycloakUserModel

Bases: UserModel, Model

PLGrid user model

Source code in yaptide/persistence/models.py
52
53
54
55
56
57
58
59
60
class KeycloakUserModel(UserModel, db.Model):
    """PLGrid user model"""

    __tablename__ = 'KeycloakUser'
    id: Column[int] = db.Column(db.Integer, db.ForeignKey('User.id', ondelete="CASCADE"), primary_key=True)
    cert: Column[str] = db.Column(db.String, nullable=True)
    private_key: Column[str] = db.Column(db.String, nullable=True)

    __mapper_args__ = {"polymorphic_identity": "KeycloakUser", "polymorphic_load": "inline"}

__mapper_args__ class-attribute instance-attribute

__mapper_args__ = {
    "polymorphic_identity": "KeycloakUser",
    "polymorphic_load": "inline",
}

__tablename__ class-attribute instance-attribute

__tablename__ = 'KeycloakUser'

cert class-attribute instance-attribute

cert = Column(String, nullable=True)

id class-attribute instance-attribute

id = Column(
    Integer,
    ForeignKey("User.id", ondelete="CASCADE"),
    primary_key=True,
)

private_key class-attribute instance-attribute

private_key = Column(String, nullable=True)

LogfilesModel

Bases: Model

Simulation logfiles model

Source code in yaptide/persistence/models.py
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
class LogfilesModel(db.Model):
    """Simulation logfiles model"""

    __tablename__ = 'Logfiles'
    id: Column[int] = db.Column(db.Integer, primary_key=True)
    simulation_id: Column[int] = db.Column(db.Integer,
                                           db.ForeignKey('Simulation.id', ondelete="CASCADE"),
                                           nullable=False)
    compressed_data: Column[bytes] = db.Column(db.LargeBinary, doc="Json object containing logfiles")

    @property
    def data(self):
        return decompress(self.compressed_data)

    @data.setter
    def data(self, value):
        if value is not None:
            self.compressed_data = compress(value)

__tablename__ class-attribute instance-attribute

__tablename__ = 'Logfiles'

compressed_data class-attribute instance-attribute

compressed_data = Column(
    LargeBinary, doc="Json object containing logfiles"
)

data property writable

data

id class-attribute instance-attribute

id = Column(Integer, primary_key=True)

simulation_id class-attribute instance-attribute

simulation_id = Column(
    Integer,
    ForeignKey("Simulation.id", ondelete="CASCADE"),
    nullable=False,
)

PageModel

Bases: Model

Estimator single page model

Source code in yaptide/persistence/models.py
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
class PageModel(db.Model):
    """Estimator single page model"""

    __tablename__ = 'Page'
    id: Column[int] = db.Column(db.Integer, primary_key=True)
    estimator_id: Column[int] = db.Column(db.Integer, db.ForeignKey('Estimator.id', ondelete="CASCADE"), nullable=False)
    page_number: Column[int] = db.Column(db.Integer, nullable=False, doc="Page number")
    compressed_data: Column[bytes] = db.Column(db.LargeBinary, doc="Page json object - data, axes and metadata")

    @property
    def data(self):
        return decompress(self.compressed_data)

    @data.setter
    def data(self, value):
        if value is not None:
            self.compressed_data = compress(value)

__tablename__ class-attribute instance-attribute

__tablename__ = 'Page'

compressed_data class-attribute instance-attribute

compressed_data = Column(
    LargeBinary,
    doc="Page json object - data, axes and metadata",
)

data property writable

data

estimator_id class-attribute instance-attribute

estimator_id = Column(
    Integer,
    ForeignKey("Estimator.id", ondelete="CASCADE"),
    nullable=False,
)

id class-attribute instance-attribute

id = Column(Integer, primary_key=True)

page_number class-attribute instance-attribute

page_number = Column(
    Integer, nullable=False, doc="Page number"
)

SimulationModel

Bases: Model

Simulation model

Source code in yaptide/persistence/models.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
class SimulationModel(db.Model):
    """Simulation model"""

    __tablename__ = 'Simulation'

    id: Column[int] = db.Column(db.Integer, primary_key=True)

    job_id: Column[str] = db.Column(db.String, nullable=False, unique=True, doc="Simulation job ID")

    user_id: Column[int] = db.Column(db.Integer, db.ForeignKey('User.id'), doc="User ID")
    start_time: Column[datetime] = db.Column(db.DateTime(timezone=True), default=now(), doc="Submission time")
    end_time: Column[datetime] = db.Column(db.DateTime(timezone=True),
                                           nullable=True,
                                           doc="Job end time (including merging)")
    title: Column[str] = db.Column(db.String, nullable=False, doc="Job title")
    platform: Column[str] = db.Column(db.String, nullable=False, doc="Execution platform name (i.e. 'direct', 'batch')")
    input_type: Column[str] = db.Column(db.String,
                                        nullable=False,
                                        doc="Input type (i.e. 'yaptide_project', 'input_files')")
    sim_type: Column[str] = db.Column(db.String,
                                      nullable=False,
                                      doc="Simulator type (i.e. 'shieldhit', 'topas', 'fluka')")
    job_state: Column[str] = db.Column(db.String,
                                       nullable=False,
                                       default=EntityState.UNKNOWN.value,
                                       doc="Simulation state (i.e. 'pending', 'running', 'completed', 'failed')")

    tasks = relationship("TaskModel", cascade="delete")
    estimators = relationship("EstimatorModel", cascade="delete")
    inputs = relationship("InputModel", cascade="delete")
    logfiles = relationship("LogfilesModel", cascade="delete")

    __mapper_args__ = {"polymorphic_identity": "Simulation", "polymorphic_on": platform, "with_polymorphic": "*"}

    def update_state(self, update_dict: dict) -> bool:
        """
        Updating database is more costly than a simple query.
        Therefore we check first if update is needed and
        perform it only for such fields which exists and which have updated values.
        Returns bool value telling if it is required to commit changes to db.
        """
        if self.job_state in (EntityState.COMPLETED.value, EntityState.FAILED.value, EntityState.CANCELED.value):
            return False
        db_commit_required = False
        if "job_state" in update_dict and self.job_state != update_dict["job_state"]:
            self.job_state = update_dict["job_state"]
            db_commit_required = True
        # Here we have a special case, `end_time` can be set only once
        # therefore we update it only if it not set previously (`self.end_time is None`)
        # and if update was requested (`"end_time" in update_dict`)
        if "end_time" in update_dict and self.end_time is None:
            # a convertion from string to datetime is needed, as in the POST payload end_time comes in string format
            self.end_time = datetime.strptime(update_dict["end_time"], '%Y-%m-%d %H:%M:%S.%f')
            db_commit_required = True
        return db_commit_required

__mapper_args__ class-attribute instance-attribute

__mapper_args__ = {
    "polymorphic_identity": "Simulation",
    "polymorphic_on": platform,
    "with_polymorphic": "*",
}

__tablename__ class-attribute instance-attribute

__tablename__ = 'Simulation'

end_time class-attribute instance-attribute

end_time = Column(
    DateTime(timezone=True),
    nullable=True,
    doc="Job end time (including merging)",
)

estimators class-attribute instance-attribute

estimators = relationship(
    "EstimatorModel", cascade="delete"
)

id class-attribute instance-attribute

id = Column(Integer, primary_key=True)

input_type class-attribute instance-attribute

input_type = Column(
    String,
    nullable=False,
    doc="Input type (i.e. 'yaptide_project', 'input_files')",
)

inputs class-attribute instance-attribute

inputs = relationship('InputModel', cascade='delete')

job_id class-attribute instance-attribute

job_id = Column(
    String,
    nullable=False,
    unique=True,
    doc="Simulation job ID",
)

job_state class-attribute instance-attribute

job_state = Column(
    String,
    nullable=False,
    default=value,
    doc="Simulation state (i.e. 'pending', 'running', 'completed', 'failed')",
)

logfiles class-attribute instance-attribute

logfiles = relationship('LogfilesModel', cascade='delete')

platform class-attribute instance-attribute

platform = Column(
    String,
    nullable=False,
    doc="Execution platform name (i.e. 'direct', 'batch')",
)

sim_type class-attribute instance-attribute

sim_type = Column(
    String,
    nullable=False,
    doc="Simulator type (i.e. 'shieldhit', 'topas', 'fluka')",
)

start_time class-attribute instance-attribute

start_time = Column(
    DateTime(timezone=True),
    default=now(),
    doc="Submission time",
)

tasks class-attribute instance-attribute

tasks = relationship('TaskModel', cascade='delete')

title class-attribute instance-attribute

title = Column(String, nullable=False, doc='Job title')

user_id class-attribute instance-attribute

user_id = Column(
    Integer, ForeignKey("User.id"), doc="User ID"
)

update_state

update_state(update_dict)

Updating database is more costly than a simple query. Therefore we check first if update is needed and perform it only for such fields which exists and which have updated values. Returns bool value telling if it is required to commit changes to db.

Source code in yaptide/persistence/models.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def update_state(self, update_dict: dict) -> bool:
    """
    Updating database is more costly than a simple query.
    Therefore we check first if update is needed and
    perform it only for such fields which exists and which have updated values.
    Returns bool value telling if it is required to commit changes to db.
    """
    if self.job_state in (EntityState.COMPLETED.value, EntityState.FAILED.value, EntityState.CANCELED.value):
        return False
    db_commit_required = False
    if "job_state" in update_dict and self.job_state != update_dict["job_state"]:
        self.job_state = update_dict["job_state"]
        db_commit_required = True
    # Here we have a special case, `end_time` can be set only once
    # therefore we update it only if it not set previously (`self.end_time is None`)
    # and if update was requested (`"end_time" in update_dict`)
    if "end_time" in update_dict and self.end_time is None:
        # a convertion from string to datetime is needed, as in the POST payload end_time comes in string format
        self.end_time = datetime.strptime(update_dict["end_time"], '%Y-%m-%d %H:%M:%S.%f')
        db_commit_required = True
    return db_commit_required

TaskModel

Bases: Model

Simulation task model

Source code in yaptide/persistence/models.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
class TaskModel(db.Model):
    """Simulation task model"""

    __tablename__ = 'Task'
    id: Column[int] = db.Column(db.Integer, primary_key=True)
    simulation_id: Column[int] = db.Column(db.Integer,
                                           db.ForeignKey('Simulation.id', ondelete="CASCADE"),
                                           doc="Simulation job ID (foreign key)")

    task_id: Column[int] = db.Column(db.Integer, nullable=False, doc="Task ID")
    requested_primaries: Column[int] = db.Column(db.Integer,
                                                 nullable=False,
                                                 default=0,
                                                 doc="Requested number of primaries")
    simulated_primaries: Column[int] = db.Column(db.Integer,
                                                 nullable=False,
                                                 default=0,
                                                 doc="Simulated number of primaries")
    task_state: Column[str] = db.Column(db.String,
                                        nullable=False,
                                        default=EntityState.PENDING.value,
                                        doc="Task state (i.e. 'pending', 'running', 'completed', 'failed')")
    estimated_time: Column[int] = db.Column(db.Integer, nullable=True, doc="Estimated time in seconds")
    start_time: Column[datetime] = db.Column(db.DateTime(timezone=True), nullable=True, doc="Task start time")
    end_time: Column[datetime] = db.Column(db.DateTime(timezone=True), nullable=True, doc="Task end time")
    platform: Column[str] = db.Column(db.String, nullable=False, doc="Execution platform name (i.e. 'direct', 'batch')")
    last_update_time: Column[datetime] = db.Column(db.DateTime(timezone=True),
                                                   default=now(),
                                                   doc="Task last update time")

    __table_args__ = (UniqueConstraint('simulation_id', 'task_id', name='_simulation_id_task_id_uc'), )

    __mapper_args__ = {"polymorphic_identity": "Task", "polymorphic_on": platform, "with_polymorphic": "*"}

    def update_state(self, update_dict: dict):  # skipcq: PY-R1000
        """
        Updating database is more costly than a simple query.
        Therefore we check first if update is needed and
        perform it only for such fields which exists and which have updated values.
        """
        if self.task_state in (EntityState.COMPLETED.value, EntityState.FAILED.value, EntityState.CANCELED.value):
            return
        if value_changed(self.requested_primaries, update_dict.get("requested_primaries")):
            self.requested_primaries = update_dict["requested_primaries"]
        if value_changed(self.simulated_primaries, update_dict.get("simulated_primaries")):
            self.simulated_primaries = update_dict["simulated_primaries"]
        if value_changed(self.task_state, update_dict.get("task_state")) and allowed_state_change(
                self.task_state, update_dict["task_state"]):
            self.task_state = update_dict["task_state"]
            if self.task_state == EntityState.COMPLETED.value:
                self.simulated_primaries = self.requested_primaries
        # Here we have a special case, `estimated_time` cannot be set when `end_time` is set - it is meaningless
        have_estim_time = "estimated_time" in update_dict and self.estimated_time != update_dict["estimated_time"]
        end_time_not_set = self.end_time is None
        if have_estim_time and end_time_not_set:
            self.estimated_time = update_dict["estimated_time"]
        if "start_time" in update_dict and self.start_time is None:
            # a convertion from string to datetime is needed, as in the POST payload start_time comes in string format
            self.start_time = datetime.strptime(update_dict["start_time"], '%Y-%m-%d %H:%M:%S.%f')
        # Here we have a special case, `end_time` can be set only once
        # therefore we update it only if it not set previously (`self.end_time is None`)
        # and if update was requested (`"end_time" in update_dict`)
        if "end_time" in update_dict and self.end_time is None:
            # a convertion from string to datetime is needed, as in the POST payload end_time comes in string format
            self.end_time = datetime.strptime(update_dict["end_time"], '%Y-%m-%d %H:%M:%S.%f')
            self.estimated_time = None
        self.last_update_time = now()

    def get_status_dict(self) -> dict:
        """Returns task information as a dictionary"""
        result = {
            "task_state": self.task_state,
            "requested_primaries": self.requested_primaries,
            "simulated_primaries": self.simulated_primaries,
            "last_update_time": self.last_update_time,
        }
        if self.estimated_time:
            result["estimated_time"] = {
                "hours": self.estimated_time // 3600,
                "minutes": (self.estimated_time // 60) % 60,
                "seconds": self.estimated_time % 60,
            }
        if self.start_time:
            result["start_time"] = self.start_time
        if self.end_time:
            result["end_time"] = self.end_time
        return result

__mapper_args__ class-attribute instance-attribute

__mapper_args__ = {
    "polymorphic_identity": "Task",
    "polymorphic_on": platform,
    "with_polymorphic": "*",
}

__table_args__ class-attribute instance-attribute

__table_args__ = UniqueConstraint(
    "simulation_id",
    "task_id",
    name="_simulation_id_task_id_uc",
)

__tablename__ class-attribute instance-attribute

__tablename__ = 'Task'

end_time class-attribute instance-attribute

end_time = Column(
    DateTime(timezone=True),
    nullable=True,
    doc="Task end time",
)

estimated_time class-attribute instance-attribute

estimated_time = Column(
    Integer, nullable=True, doc="Estimated time in seconds"
)

id class-attribute instance-attribute

id = Column(Integer, primary_key=True)

last_update_time class-attribute instance-attribute

last_update_time = Column(
    DateTime(timezone=True),
    default=now(),
    doc="Task last update time",
)

platform class-attribute instance-attribute

platform = Column(
    String,
    nullable=False,
    doc="Execution platform name (i.e. 'direct', 'batch')",
)

requested_primaries class-attribute instance-attribute

requested_primaries = Column(
    Integer,
    nullable=False,
    default=0,
    doc="Requested number of primaries",
)

simulated_primaries class-attribute instance-attribute

simulated_primaries = Column(
    Integer,
    nullable=False,
    default=0,
    doc="Simulated number of primaries",
)

simulation_id class-attribute instance-attribute

simulation_id = Column(
    Integer,
    ForeignKey("Simulation.id", ondelete="CASCADE"),
    doc="Simulation job ID (foreign key)",
)

start_time class-attribute instance-attribute

start_time = Column(
    DateTime(timezone=True),
    nullable=True,
    doc="Task start time",
)

task_id class-attribute instance-attribute

task_id = Column(Integer, nullable=False, doc='Task ID')

task_state class-attribute instance-attribute

task_state = Column(
    String,
    nullable=False,
    default=value,
    doc="Task state (i.e. 'pending', 'running', 'completed', 'failed')",
)

get_status_dict

get_status_dict()

Returns task information as a dictionary

Source code in yaptide/persistence/models.py
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
def get_status_dict(self) -> dict:
    """Returns task information as a dictionary"""
    result = {
        "task_state": self.task_state,
        "requested_primaries": self.requested_primaries,
        "simulated_primaries": self.simulated_primaries,
        "last_update_time": self.last_update_time,
    }
    if self.estimated_time:
        result["estimated_time"] = {
            "hours": self.estimated_time // 3600,
            "minutes": (self.estimated_time // 60) % 60,
            "seconds": self.estimated_time % 60,
        }
    if self.start_time:
        result["start_time"] = self.start_time
    if self.end_time:
        result["end_time"] = self.end_time
    return result

update_state

update_state(update_dict)

Updating database is more costly than a simple query. Therefore we check first if update is needed and perform it only for such fields which exists and which have updated values.

Source code in yaptide/persistence/models.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def update_state(self, update_dict: dict):  # skipcq: PY-R1000
    """
    Updating database is more costly than a simple query.
    Therefore we check first if update is needed and
    perform it only for such fields which exists and which have updated values.
    """
    if self.task_state in (EntityState.COMPLETED.value, EntityState.FAILED.value, EntityState.CANCELED.value):
        return
    if value_changed(self.requested_primaries, update_dict.get("requested_primaries")):
        self.requested_primaries = update_dict["requested_primaries"]
    if value_changed(self.simulated_primaries, update_dict.get("simulated_primaries")):
        self.simulated_primaries = update_dict["simulated_primaries"]
    if value_changed(self.task_state, update_dict.get("task_state")) and allowed_state_change(
            self.task_state, update_dict["task_state"]):
        self.task_state = update_dict["task_state"]
        if self.task_state == EntityState.COMPLETED.value:
            self.simulated_primaries = self.requested_primaries
    # Here we have a special case, `estimated_time` cannot be set when `end_time` is set - it is meaningless
    have_estim_time = "estimated_time" in update_dict and self.estimated_time != update_dict["estimated_time"]
    end_time_not_set = self.end_time is None
    if have_estim_time and end_time_not_set:
        self.estimated_time = update_dict["estimated_time"]
    if "start_time" in update_dict and self.start_time is None:
        # a convertion from string to datetime is needed, as in the POST payload start_time comes in string format
        self.start_time = datetime.strptime(update_dict["start_time"], '%Y-%m-%d %H:%M:%S.%f')
    # Here we have a special case, `end_time` can be set only once
    # therefore we update it only if it not set previously (`self.end_time is None`)
    # and if update was requested (`"end_time" in update_dict`)
    if "end_time" in update_dict and self.end_time is None:
        # a convertion from string to datetime is needed, as in the POST payload end_time comes in string format
        self.end_time = datetime.strptime(update_dict["end_time"], '%Y-%m-%d %H:%M:%S.%f')
        self.estimated_time = None
    self.last_update_time = now()

UserModel

Bases: Model

User model

Source code in yaptide/persistence/models.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class UserModel(db.Model):
    """User model"""

    __tablename__ = 'User'
    id: Column[int] = db.Column(db.Integer, primary_key=True)
    username: Column[str] = db.Column(db.String, nullable=False)
    auth_provider: Column[str] = db.Column(db.String, nullable=False)
    simulations = relationship("SimulationModel")

    __table_args__ = (UniqueConstraint('username', 'auth_provider', name='_username_provider_uc'), )

    __mapper_args__ = {"polymorphic_identity": "User", "polymorphic_on": auth_provider, "with_polymorphic": "*"}

    def __repr__(self) -> str:
        return f'User #{self.id} {self.username}'

__mapper_args__ class-attribute instance-attribute

__mapper_args__ = {
    "polymorphic_identity": "User",
    "polymorphic_on": auth_provider,
    "with_polymorphic": "*",
}

__table_args__ class-attribute instance-attribute

__table_args__ = UniqueConstraint(
    "username",
    "auth_provider",
    name="_username_provider_uc",
)

__tablename__ class-attribute instance-attribute

__tablename__ = 'User'

auth_provider class-attribute instance-attribute

auth_provider = Column(String, nullable=False)

id class-attribute instance-attribute

id = Column(Integer, primary_key=True)

simulations class-attribute instance-attribute

simulations = relationship('SimulationModel')

username class-attribute instance-attribute

username = Column(String, nullable=False)

__repr__

__repr__()
Source code in yaptide/persistence/models.py
30
31
def __repr__(self) -> str:
    return f'User #{self.id} {self.username}'

YaptideUserModel

Bases: UserModel, Model

Yaptide user model

Source code in yaptide/persistence/models.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class YaptideUserModel(UserModel, db.Model):
    """Yaptide user model"""

    __tablename__ = 'YaptideUser'
    id: Column[int] = db.Column(db.Integer, db.ForeignKey('User.id', ondelete="CASCADE"), primary_key=True)
    password_hash: Column[str] = db.Column(db.String, nullable=False)

    __mapper_args__ = {"polymorphic_identity": "YaptideUser", "polymorphic_load": "inline"}

    def set_password(self, password: str):
        """Sets hashed password"""
        self.password_hash = generate_password_hash(password)

    def check_password(self, password: str) -> bool:
        """Checks password correctness"""
        return check_password_hash(self.password_hash, password)

__mapper_args__ class-attribute instance-attribute

__mapper_args__ = {
    "polymorphic_identity": "YaptideUser",
    "polymorphic_load": "inline",
}

__tablename__ class-attribute instance-attribute

__tablename__ = 'YaptideUser'

id class-attribute instance-attribute

id = Column(
    Integer,
    ForeignKey("User.id", ondelete="CASCADE"),
    primary_key=True,
)

password_hash class-attribute instance-attribute

password_hash = Column(String, nullable=False)

check_password

check_password(password)

Checks password correctness

Source code in yaptide/persistence/models.py
47
48
49
def check_password(self, password: str) -> bool:
    """Checks password correctness"""
    return check_password_hash(self.password_hash, password)

set_password

set_password(password)

Sets hashed password

Source code in yaptide/persistence/models.py
43
44
45
def set_password(self, password: str):
    """Sets hashed password"""
    self.password_hash = generate_password_hash(password)

allowed_state_change

allowed_state_change(current_state, next_state)

Ensures that no such change like Completed -> Canceled happens

Source code in yaptide/persistence/models.py
166
167
168
169
def allowed_state_change(current_state: str, next_state: str):
    """Ensures that no such change like Completed -> Canceled happens"""
    return not (current_state in [EntityState.FAILED.value, EntityState.COMPLETED.value]
                and next_state in [EntityState.CANCELED])

compress

compress(data)

Serializes JSON and compresses data

Source code in yaptide/persistence/models.py
302
303
304
305
306
307
308
309
310
311
def compress(data) -> bytes:
    """Serializes JSON and compresses data"""
    compressed_bytes = b''
    if data is not None:
        # Serialize the JSON
        serialized_data: str = json.dumps(data)
        # Compress the data
        bytes_to_compress: bytes = serialized_data.encode('utf-8')
        compressed_bytes = gzip.compress(bytes_to_compress)
    return compressed_bytes

create_all

create_all()

Creates all tables, to be used with Flask app context.

Source code in yaptide/persistence/models.py
393
394
395
def create_all():
    """Creates all tables, to be used with Flask app context."""
    db.create_all()

decompress

decompress(data)

Decompresses data and deserializes JSON

Source code in yaptide/persistence/models.py
291
292
293
294
295
296
297
298
299
def decompress(data: bytes):
    """Decompresses data and deserializes JSON"""
    data_to_unpack: str = 'null'
    if data is not None:
        # Decompress the data
        decompressed_bytes: bytes = gzip.decompress(data)
        data_to_unpack = decompressed_bytes.decode('utf-8')
        # Deserialize the JSON
    return json.loads(data_to_unpack)

value_changed

value_changed(current_value, new_value)

checks if value from update_dict differs from object in database

Source code in yaptide/persistence/models.py
172
173
174
def value_changed(current_value: str, new_value: str):
    """checks if value from update_dict differs from object in database"""
    return new_value and current_value != new_value