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

SwissDataScienceCenter / renku-data-services / 14831500481

05 May 2025 07:40AM UTC coverage: 86.561% (-0.1%) from 86.66%
14831500481

Pull #821

github

web-flow
Merge b48eda802 into d90537ab5
Pull Request #821: fix: resolve clashing mount points issue with data connectors

4 of 33 new or added lines in 1 file covered. (12.12%)

6 existing lines in 4 files now uncovered.

20508 of 23692 relevant lines covered (86.56%)

1.53 hits per line

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

95.05
/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

54

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

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

63

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

68
    id: ULID
2✔
69

70

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

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

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

104

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

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

122

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

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

131

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

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

151

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

157

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

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

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

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

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

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

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

200

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

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

214

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

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

224

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

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

238

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

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

248

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

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

257

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

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

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

275

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

280
    environment_id: ULID
2✔
281

282

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

287
    status: BuildStatus | None = None
2✔
288

289

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

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

308

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

313
    status: BuildStatus
2✔
314
    result: BuildResult | None = None
2✔
315
    completed_at: datetime | None = None
2✔
316
    error_reason: str | None = None
2✔
317

318

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

323
    update: ShipwrightBuildStatusUpdateContent | None
2✔
324
    """The update about a build.
2✔
325

326
    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