11"""Models for sessions."""
22
3+ import typing
34from dataclasses import dataclass
45from datetime import datetime , timedelta
56from enum import StrEnum
67from pathlib import PurePosixPath
8+ from typing import TYPE_CHECKING
79
810from ulid import ULID
911
1012from renku_data_services import errors
1113from renku_data_services .base_models .core import ResetType
1214from renku_data_services .session import crs
1315
16+ if TYPE_CHECKING :
17+ from renku_data_services .session import apispec
18+
19+ from .constants import ENV_VARIABLE_NAME_MATCHER , ENV_VARIABLE_REGEX
20+
1421
1522@dataclass (frozen = True , eq = True , kw_only = True )
1623class Member :
@@ -142,6 +149,55 @@ class EnvironmentPatch:
142149 environment_image_source : EnvironmentImageSource | None = None
143150
144151
152+ # TODO: Verify that these limits are compatible with k8s
153+ MAX_NUMBER_ENV_VARIABLES : typing .Final [int ] = 32
154+ MAX_LENGTH_ENV_VARIABLES_NAME : typing .Final [int ] = 256
155+ MAX_LENGTH_ENV_VARIABLES_VALUE : typing .Final [int ] = 1000
156+
157+
158+ @dataclass (frozen = True , eq = True , kw_only = True )
159+ class EnvVar :
160+ """Model for an environment variable."""
161+
162+ name : str
163+ value : str | None = None
164+
165+ @classmethod
166+ def from_dict (cls , env_dict : dict [str , str | None ]) -> list ["EnvVar" ]:
167+ """Create a list of EnvVar instances from a dictionary."""
168+ return [cls (name = name , value = value ) for name , value in env_dict .items ()]
169+
170+ @classmethod
171+ def from_apispec (cls , env_variables : list ["apispec.EnvVar" ]) -> list ["EnvVar" ]:
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 ]
174+
175+ @classmethod
176+ def to_dict (cls , env_variables : list ["EnvVar" ]) -> dict [str , str | None ]:
177+ """Convert to dict."""
178+ return {var .name : var .value for var in env_variables }
179+
180+ def __post_init__ (self ) -> None :
181+ error_msgs : list [str ] = []
182+ if len (self .name ) > MAX_LENGTH_ENV_VARIABLES_NAME :
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" ):
187+ error_msgs .append (f"Env variable name '{ self .name } ' should not start with 'RENKU'." )
188+ if ENV_VARIABLE_NAME_MATCHER .match (self .name ) is None :
189+ error_msgs .append (f"Env variable name '{ self .name } ' must match the regex '{ ENV_VARIABLE_REGEX } '." )
190+ if self .value and len (self .value ) > MAX_LENGTH_ENV_VARIABLES_VALUE :
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 :
196+ if len (error_msgs ) == 1 :
197+ raise errors .ValidationError (message = error_msgs [0 ])
198+ raise errors .ValidationError (message = "\n " .join (error_msgs ))
199+
200+
145201@dataclass (frozen = True , eq = True , kw_only = True )
146202class UnsavedSessionLauncher :
147203 """Session launcher model that has not been persisted in the DB."""
@@ -151,6 +207,7 @@ class UnsavedSessionLauncher:
151207 description : str | None
152208 resource_class_id : int | None
153209 disk_storage : int | None
210+ env_variables : list [EnvVar ] | None
154211 environment : str | UnsavedEnvironment | UnsavedBuildParameters
155212 """When a string is passed for the environment it should be the ID of an existing environment."""
156213
@@ -176,6 +233,7 @@ class SessionLauncherPatch:
176233 environment : str | EnvironmentPatch | UnsavedEnvironment | UnsavedBuildParameters | None = None
177234 resource_class_id : int | None | ResetType = None
178235 disk_storage : int | None | ResetType = None
236+ env_variables : list [EnvVar ] | None | ResetType = None
179237
180238
181239@dataclass (frozen = True , eq = True , kw_only = True )
0 commit comments