• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SwissDataScienceCenter / renku-data-services / 15844898279

24 Jun 2025 08:06AM UTC coverage: 86.98% (-0.02%) from 86.999%
15844898279

Pull #903

github

web-flow
Merge 62759d788 into 4f5366ae7
Pull Request #903: feat: update to rclone v1.70.0+renku-1

2 of 2 new or added lines in 2 files covered. (100.0%)

6 existing lines in 4 files now uncovered.

21859 of 25131 relevant lines covered (86.98%)

1.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.12
/components/renku_data_services/session/models.py
1
"""Models for sessions."""
2

3
import typing
2✔
4
from dataclasses import dataclass
2✔
5
from datetime import datetime, timedelta
2✔
6
from enum import StrEnum
2✔
7
from pathlib import PurePosixPath
2✔
8
from typing import TYPE_CHECKING
2✔
9

10
from ulid import ULID
2✔
11

12
from renku_data_services import errors
2✔
13
from renku_data_services.base_models.core import ResetType
2✔
14
from renku_data_services.session import crs
2✔
15

16
if TYPE_CHECKING:
2✔
17
    from renku_data_services.session import apispec
×
18

19
from .constants import ENV_VARIABLE_NAME_MATCHER, ENV_VARIABLE_REGEX
2✔
20

21

22
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
23
class Member:
2✔
24
    """Member model."""
25

26
    id: str
2✔
27

28

29
class EnvironmentKind(StrEnum):
2✔
30
    """The type of environment."""
31

32
    GLOBAL = "GLOBAL"
2✔
33
    CUSTOM = "CUSTOM"
2✔
34

35

36
class EnvironmentImageSource(StrEnum):
2✔
37
    """The source of the environment image."""
38

39
    image = "image"
2✔
40
    build = "build"
2✔
41

42

43
class BuilderVariant(StrEnum):
2✔
44
    """The type of environment builder."""
45

46
    python = "python"
2✔
47

48

49
class FrontendVariant(StrEnum):
2✔
50
    """The environment frontend choice."""
51

52
    vscodium = "vscodium"
2✔
53
    jupyterlab = "jupyterlab"
2✔
54

55

56
@dataclass(kw_only=True, frozen=True, eq=True)
2✔
57
class UnsavedBuildParameters:
2✔
58
    """The parameters of a build."""
59

60
    repository: str
2✔
61
    builder_variant: str
2✔
62
    frontend_variant: str
2✔
63

64

65
@dataclass(kw_only=True, frozen=True, eq=True)
2✔
66
class BuildParameters(UnsavedBuildParameters):
2✔
67
    """BuildParameters saved in the database."""
68

69
    id: ULID
2✔
70

71

72
@dataclass(kw_only=True, frozen=True, eq=True)
2✔
73
class UnsavedEnvironment:
2✔
74
    """Session environment model that has not been saved."""
75

76
    name: str
2✔
77
    description: str | None = None
2✔
78
    container_image: str
2✔
79
    default_url: str
2✔
80
    port: int = 8888
2✔
81
    working_directory: PurePosixPath | None = None
2✔
82
    mount_directory: PurePosixPath | None = None
2✔
83
    uid: int = 1000
2✔
84
    gid: int = 1000
2✔
85
    environment_kind: EnvironmentKind
2✔
86
    environment_image_source: EnvironmentImageSource
2✔
87
    args: list[str] | None = None
2✔
88
    command: list[str] | None = None
2✔
89
    is_archived: bool = False
2✔
90

91
    def __post_init__(self) -> None:
2✔
92
        if self.working_directory and not self.working_directory.is_absolute():
2✔
93
            raise errors.ValidationError(message="The working directory for a session is supposed to be absolute")
×
94
        if self.mount_directory and not self.mount_directory.is_absolute():
2✔
UNCOV
95
            raise errors.ValidationError(message="The mount directory for a session is supposed to be absolute")
×
96
        if self.working_directory and self.working_directory.is_reserved():
2✔
97
            raise errors.ValidationError(
×
98
                message="The requested value for the working directory is reserved by the OS and cannot be used."
99
            )
100
        if self.mount_directory and self.mount_directory.is_reserved():
2✔
101
            raise errors.ValidationError(
×
102
                message="The requested value for the mount directory is reserved by the OS and cannot be used."
103
            )
104

105

106
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
107
class Environment(UnsavedEnvironment):
2✔
108
    """Session environment model."""
109

110
    id: ULID
2✔
111
    creation_date: datetime
2✔
112
    created_by: Member
2✔
113
    container_image: str
2✔
114
    default_url: str
2✔
115
    port: int
2✔
116
    working_directory: PurePosixPath | None
2✔
117
    mount_directory: PurePosixPath | None
2✔
118
    uid: int
2✔
119
    gid: int
2✔
120
    build_parameters: BuildParameters | None
2✔
121
    build_parameters_id: ULID | None
2✔
122

123

124
@dataclass(kw_only=True, frozen=True, eq=True)
2✔
125
class BuildParametersPatch:
2✔
126
    """Patch for parameters of a build."""
127

128
    repository: str | None = None
2✔
129
    builder_variant: str | None = None
2✔
130
    frontend_variant: str | None = None
2✔
131

132

133
@dataclass(eq=True, kw_only=True)
2✔
134
class EnvironmentPatch:
2✔
135
    """Model for changes requested on a session environment."""
136

137
    name: str | None = None
2✔
138
    description: str | None = None
2✔
139
    container_image: str | None = None
2✔
140
    default_url: str | None = None
2✔
141
    port: int | None = None
2✔
142
    working_directory: PurePosixPath | ResetType | None = None
2✔
143
    mount_directory: PurePosixPath | ResetType | None = None
2✔
144
    uid: int | None = None
2✔
145
    gid: int | None = None
2✔
146
    args: list[str] | None | ResetType = None
2✔
147
    command: list[str] | None | ResetType = None
2✔
148
    is_archived: bool | None = None
2✔
149
    build_parameters: BuildParametersPatch | None = None
2✔
150
    environment_image_source: EnvironmentImageSource | None = None
2✔
151

152

153
# TODO: Verify that these limits are compatible with k8s
154
MAX_NUMBER_ENV_VARIABLES: typing.Final[int] = 32
2✔
155
MAX_LENGTH_ENV_VARIABLES_NAME: typing.Final[int] = 256
2✔
156
MAX_LENGTH_ENV_VARIABLES_VALUE: typing.Final[int] = 1000
2✔
157

158

159
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
160
class EnvVar:
2✔
161
    """Model for an environment variable."""
162

163
    name: str
2✔
164
    value: str | None = None
2✔
165

166
    @classmethod
2✔
167
    def from_dict(cls, env_dict: dict[str, str | None]) -> list["EnvVar"]:
2✔
168
        """Create a list of EnvVar instances from a dictionary."""
169
        return [cls(name=name, value=value) for name, value in env_dict.items()]
1✔
170

171
    @classmethod
2✔
172
    def from_apispec(cls, env_variables: list["apispec.EnvVar"]) -> list["EnvVar"]:
2✔
173
        """Create a list of EnvVar instances from apispec objects."""
174
        return [cls(name=env_var.name, value=env_var.value) for env_var in env_variables]
2✔
175

176
    @classmethod
2✔
177
    def to_dict(cls, env_variables: list["EnvVar"]) -> dict[str, str | None]:
2✔
178
        """Convert to dict."""
179
        return {var.name: var.value for var in env_variables}
1✔
180

181
    def __post_init__(self) -> None:
2✔
182
        error_msgs: list[str] = []
2✔
183
        if len(self.name) > MAX_LENGTH_ENV_VARIABLES_NAME:
2✔
184
            error_msgs.append(
×
185
                f"Env variable name '{self.name}' is longer than {MAX_LENGTH_ENV_VARIABLES_NAME} characters."
186
            )
187
        if self.name.upper().startswith("RENKU"):
2✔
188
            error_msgs.append(f"Env variable name '{self.name}' should not start with 'RENKU'.")
1✔
189
        if ENV_VARIABLE_NAME_MATCHER.match(self.name) is None:
2✔
190
            error_msgs.append(f"Env variable name '{self.name}' must match the regex '{ENV_VARIABLE_REGEX}'.")
1✔
191
        if self.value and len(self.value) > MAX_LENGTH_ENV_VARIABLES_VALUE:
2✔
192
            error_msgs.append(
×
193
                f"Env variable value for '{self.name}' is longer than {MAX_LENGTH_ENV_VARIABLES_VALUE} characters."
194
            )
195

196
        if error_msgs:
2✔
197
            if len(error_msgs) == 1:
1✔
198
                raise errors.ValidationError(message=error_msgs[0])
1✔
199
            raise errors.ValidationError(message="\n".join(error_msgs))
×
200

201

202
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
203
class UnsavedSessionLauncher:
2✔
204
    """Session launcher model that has not been persisted in the DB."""
205

206
    project_id: ULID
2✔
207
    name: str
2✔
208
    description: str | None
2✔
209
    resource_class_id: int | None
2✔
210
    disk_storage: int | None
2✔
211
    env_variables: list[EnvVar] | None
2✔
212
    environment: str | UnsavedEnvironment | UnsavedBuildParameters
2✔
213
    """When a string is passed for the environment it should be the ID of an existing environment."""
2✔
214

215

216
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
217
class SessionLauncher(UnsavedSessionLauncher):
2✔
218
    """Session launcher model."""
219

220
    id: ULID
2✔
221
    creation_date: datetime
2✔
222
    created_by: Member
2✔
223
    environment: Environment
2✔
224

225

226
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
227
class SessionLauncherPatch:
2✔
228
    """Model for changes requested on a session launcher."""
229

230
    name: str | None = None
2✔
231
    description: str | None = None
2✔
232
    # NOTE: When unsaved environment is used it means a brand-new environment should be created for the
233
    # launcher with the update of the launcher.
234
    environment: str | EnvironmentPatch | UnsavedEnvironment | UnsavedBuildParameters | None = None
2✔
235
    resource_class_id: int | None | ResetType = None
2✔
236
    disk_storage: int | None | ResetType = None
2✔
237
    env_variables: list[EnvVar] | None | ResetType = None
2✔
238

239

240
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
241
class BuildResult:
2✔
242
    """Model to represent the result of a build of a container image."""
243

244
    image: str
2✔
245
    completed_at: datetime
2✔
246
    repository_url: str
2✔
247
    repository_git_commit_sha: str
2✔
248

249

250
class BuildStatus(StrEnum):
2✔
251
    """The status of a build."""
252

253
    in_progress = "in_progress"
2✔
254
    failed = "failed"
2✔
255
    cancelled = "cancelled"
2✔
256
    succeeded = "succeeded"
2✔
257

258

259
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
260
class Build:
2✔
261
    """Model to represent the build of a container image."""
262

263
    id: ULID
2✔
264
    environment_id: ULID
2✔
265
    created_at: datetime
2✔
266
    status: BuildStatus
2✔
267
    result: BuildResult | None = None
2✔
268
    error_reason: str | None = None
2✔
269

270
    @property
2✔
271
    def k8s_name(self) -> str:
2✔
272
        """Returns the name of the corresponding Shipwright BuildRun."""
273
        name = f"renku-{self.id}"
×
274
        return name.lower()
×
275

276

277
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
278
class UnsavedBuild:
2✔
279
    """Model to represent a requested container image build."""
280

281
    environment_id: ULID
2✔
282

283

284
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
285
class BuildPatch:
2✔
286
    """Model to represent the requested update to a container image build."""
287

288
    status: BuildStatus | None = None
2✔
289

290

291
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
292
class ShipwrightBuildRunParams:
2✔
293
    """Model to represent the parameters used to create a new Shipwright BuildRun."""
294

295
    name: str
2✔
296
    git_repository: str
2✔
297
    run_image: str
2✔
298
    output_image: str
2✔
299
    build_strategy_name: str
2✔
300
    push_secret_name: str
2✔
301
    retention_after_failed: timedelta | None = None
2✔
302
    retention_after_succeeded: timedelta | None = None
2✔
303
    build_timeout: timedelta | None = None
2✔
304
    node_selector: dict[str, str] | None = None
2✔
305
    tolerations: list[crs.Toleration] | None = None
2✔
306
    labels: dict[str, str] | None = None
2✔
307
    annotations: dict[str, str] | None = None
2✔
308
    frontend: str = FrontendVariant.vscodium.value
2✔
309
    build_image: str | None = None
2✔
310

311

312
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
313
class ShipwrightBuildStatusUpdateContent:
2✔
314
    """Model to represent an update about a build from Shipwright."""
315

316
    status: BuildStatus
2✔
317
    result: BuildResult | None = None
2✔
318
    completed_at: datetime | None = None
2✔
319
    error_reason: str | None = None
2✔
320

321

322
@dataclass(frozen=True, eq=True, kw_only=True)
2✔
323
class ShipwrightBuildStatusUpdate:
2✔
324
    """Model to represent an update about a build from Shipwright."""
325

326
    update: ShipwrightBuildStatusUpdateContent | None
2✔
327
    """The update about a build.
2✔
328

329
    None represents "no update"."""
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc