1010
1111
1212def normalize_endpoint (endpoint : str ) -> str :
13- """Normalize endpoint to be added into the URL."""
13+ """Normalize endpoint to be added into the URL.
14+
15+ Ensure an endpoint string is suitable for inclusion in a URL.
16+
17+ Removes any double-quote characters and prepends a leading slash if one is
18+ not already present.
19+
20+ Parameters:
21+ endpoint (str): The endpoint string to normalize.
22+
23+ Returns:
24+ str: The normalized endpoint starting with '/' and containing no double-quote characters.
25+ """
1426 endpoint = endpoint .replace ('"' , "" )
1527 if not endpoint .startswith ("/" ):
1628 endpoint = "/" + endpoint
1729 return endpoint
1830
1931
2032def validate_json (message : Any , schema : Any ) -> None :
21- """Check the JSON message with the given schema."""
33+ """Check the JSON message with the given schema.
34+
35+ Validate a JSON-like object against a jsonschema-compatible schema.
36+
37+ Parameters:
38+ message (Any): The JSON-like instance to validate (typically a dict or list).
39+ schema (Any): A jsonschema-compatible schema describing the expected structure.
40+
41+ Returns:
42+ None
43+
44+ Raises:
45+ AssertionError: If the instance does not conform to the schema or if
46+ the schema itself is invalid; the assertion message contains the
47+ underlying jsonschema error.
48+ """
2249 try :
2350 jsonschema .validate (
2451 instance = message ,
@@ -33,7 +60,23 @@ def validate_json(message: Any, schema: Any) -> None:
3360
3461
3562def wait_for_container_health (container_name : str , max_attempts : int = 3 ) -> None :
36- """Wait for container to be healthy."""
63+ """Wait for container to be healthy.
64+
65+ Polls a Docker container until its health status becomes `healthy` or the
66+ attempt limit is reached.
67+
68+ Checks the container's `Health.Status` using `docker inspect` up to
69+ `max_attempts`, printing progress and final status messages. Transient
70+ inspect errors or timeouts are ignored and retried; the function returns
71+ after the container is observed healthy or after all attempts complete.
72+
73+ Returns:
74+ None
75+
76+ Parameters:
77+ container_name (str): Docker container name or ID to check.
78+ max_attempts (int): Maximum number of health check attempts (default 3).
79+ """
3780 for attempt in range (max_attempts ):
3881 try :
3982 result = subprocess .run (
@@ -71,6 +114,13 @@ def validate_json_partially(actual: Any, expected: Any) -> None:
71114 """Recursively validate that `actual` JSON contains all keys and values specified in `expected`.
72115
73116 Extra elements/keys are ignored. Raises AssertionError if validation fails.
117+
118+ Returns:
119+ None
120+
121+ Raises:
122+ AssertionError: If a required key is missing, no list element matches
123+ an expected item, or a value does not equal the expected value.
74124 """
75125 if isinstance (expected , dict ):
76126 for key , expected_value in expected .items ():
@@ -98,7 +148,24 @@ def validate_json_partially(actual: Any, expected: Any) -> None:
98148def switch_config (
99149 source_path : str , destination_path : str = "lightspeed-stack.yaml"
100150) -> None :
101- """Overwrite the config in `destination_path` by `source_path`."""
151+ """Overwrite the config in `destination_path` by `source_path`.
152+
153+ Replace the destination configuration file with the file at source_path.
154+
155+ Parameters:
156+ source_path (str): Path to the replacement configuration file.
157+ destination_path (str): Path to the configuration file to be
158+ overwritten (defaults to "lightspeed-stack.yaml").
159+
160+ Returns:
161+ None
162+
163+ Raises:
164+ FileNotFoundError: If source_path does not exist.
165+ PermissionError: If the file cannot be read or destination cannot be
166+ written due to permissions.
167+ OSError: For other OS-related failures during the copy operation.
168+ """
102169 try :
103170 shutil .copy (source_path , destination_path )
104171 except (FileNotFoundError , PermissionError , OSError ) as e :
@@ -107,7 +174,19 @@ def switch_config(
107174
108175
109176def create_config_backup (config_path : str ) -> str :
110- """Create a backup of `config_path` if it does not already exist."""
177+ """Create a backup of `config_path` if it does not already exist.
178+
179+ Ensure a backup of the given configuration file exists by creating a
180+ `.backup` copy if it is missing.
181+
182+ Returns:
183+ str: Path to the backup file (original path with `.backup` appended).
184+
185+ Raises:
186+ FileNotFoundError: If the source config file does not exist.
187+ PermissionError: If the process lacks permission to read or write the files.
188+ OSError: For other OS-level errors encountered while copying.
189+ """
111190 backup_file = f"{ config_path } .backup"
112191 if not os .path .exists (backup_file ):
113192 try :
@@ -119,7 +198,18 @@ def create_config_backup(config_path: str) -> str:
119198
120199
121200def remove_config_backup (backup_path : str ) -> None :
122- """Delete the backup file at `backup_path` if it exists."""
201+ """Delete the backup file at `backup_path` if it exists.
202+
203+ Remove the backup file at the given path if it exists.
204+
205+ If the file is present, attempts to delete it; on failure prints a warning with the error.
206+
207+ Returns:
208+ None
209+
210+ Parameters:
211+ backup_path (str): Filesystem path to the backup file to remove.
212+ """
123213 if os .path .exists (backup_path ):
124214 try :
125215 os .remove (backup_path )
@@ -128,7 +218,15 @@ def remove_config_backup(backup_path: str) -> None:
128218
129219
130220def restart_container (container_name : str ) -> None :
131- """Restart a Docker container by name and wait until it is healthy."""
221+ """Restart a Docker container by name and wait until it is healthy.
222+
223+ Returns:
224+ None
225+
226+ Raises:
227+ subprocess.CalledProcessError: if the `docker restart` command fails.
228+ subprocess.TimeoutExpired: if the `docker restart` command times out.
229+ """
132230 try :
133231 subprocess .run (
134232 ["docker" , "restart" , container_name ],
@@ -147,13 +245,12 @@ def restart_container(container_name: str) -> None:
147245def replace_placeholders (context : Context , text : str ) -> str :
148246 """Replace {MODEL} and {PROVIDER} placeholders with actual values from context.
149247
150- Args :
151- context: Behave context containing default_model and default_provider
152- text: String that may contain {MODEL} and {PROVIDER} placeholders
248+ Parameters :
249+ context (Context) : Behave context containing default_model and default_provider
250+ text (str) : String that may contain {MODEL} and {PROVIDER} placeholders
153251
154252 Returns:
155253 String with placeholders replaced by actual values
156-
157254 """
158255 result = text .replace ("{MODEL}" , context .default_model )
159256 result = result .replace ("{PROVIDER}" , context .default_provider )
0 commit comments