iris-devtester 1.8.1__py3-none-any.whl → 1.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. iris_devtester/__init__.py +3 -2
  2. iris_devtester/cli/__init__.py +4 -2
  3. iris_devtester/cli/__main__.py +1 -1
  4. iris_devtester/cli/connection_commands.py +31 -51
  5. iris_devtester/cli/container.py +42 -113
  6. iris_devtester/cli/container_commands.py +6 -4
  7. iris_devtester/cli/fixture_commands.py +97 -73
  8. iris_devtester/config/auto_discovery.py +8 -20
  9. iris_devtester/config/container_config.py +24 -35
  10. iris_devtester/config/container_state.py +19 -43
  11. iris_devtester/config/discovery.py +10 -10
  12. iris_devtester/config/presets.py +3 -10
  13. iris_devtester/config/yaml_loader.py +3 -2
  14. iris_devtester/connections/__init__.py +25 -30
  15. iris_devtester/connections/connection.py +4 -3
  16. iris_devtester/connections/dbapi.py +5 -1
  17. iris_devtester/connections/jdbc.py +2 -6
  18. iris_devtester/connections/manager.py +1 -1
  19. iris_devtester/connections/retry.py +2 -5
  20. iris_devtester/containers/__init__.py +6 -6
  21. iris_devtester/containers/cpf_manager.py +13 -12
  22. iris_devtester/containers/iris_container.py +268 -436
  23. iris_devtester/containers/models.py +18 -43
  24. iris_devtester/containers/monitor_utils.py +1 -3
  25. iris_devtester/containers/monitoring.py +31 -46
  26. iris_devtester/containers/performance.py +5 -5
  27. iris_devtester/containers/validation.py +27 -60
  28. iris_devtester/containers/wait_strategies.py +13 -4
  29. iris_devtester/fixtures/__init__.py +14 -13
  30. iris_devtester/fixtures/creator.py +127 -555
  31. iris_devtester/fixtures/loader.py +221 -78
  32. iris_devtester/fixtures/manifest.py +8 -6
  33. iris_devtester/fixtures/obj_export.py +45 -35
  34. iris_devtester/fixtures/validator.py +4 -7
  35. iris_devtester/integrations/langchain.py +2 -6
  36. iris_devtester/ports/registry.py +5 -4
  37. iris_devtester/testing/__init__.py +3 -0
  38. iris_devtester/testing/fixtures.py +10 -1
  39. iris_devtester/testing/helpers.py +5 -12
  40. iris_devtester/testing/models.py +3 -2
  41. iris_devtester/testing/schema_reset.py +1 -3
  42. iris_devtester/utils/__init__.py +20 -5
  43. iris_devtester/utils/container_port.py +2 -6
  44. iris_devtester/utils/container_status.py +2 -6
  45. iris_devtester/utils/dbapi_compat.py +29 -14
  46. iris_devtester/utils/enable_callin.py +5 -7
  47. iris_devtester/utils/health_checks.py +18 -33
  48. iris_devtester/utils/iris_container_adapter.py +27 -26
  49. iris_devtester/utils/password.py +673 -0
  50. iris_devtester/utils/progress.py +1 -1
  51. iris_devtester/utils/test_connection.py +4 -6
  52. {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/METADATA +7 -7
  53. iris_devtester-1.9.1.dist-info/RECORD +66 -0
  54. {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/WHEEL +1 -1
  55. iris_devtester/utils/password_reset.py +0 -594
  56. iris_devtester/utils/password_verification.py +0 -350
  57. iris_devtester/utils/unexpire_passwords.py +0 -168
  58. iris_devtester-1.8.1.dist-info/RECORD +0 -68
  59. {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/entry_points.txt +0 -0
  60. {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/licenses/LICENSE +0 -0
  61. {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/top_level.txt +0 -0
@@ -1,25 +1,29 @@
1
1
  import logging
2
- import time
3
2
  import subprocess
3
+ import time
4
4
  from pathlib import Path
5
- from typing import List, Optional
6
5
  from subprocess import TimeoutExpired
6
+ from typing import List, Optional
7
7
 
8
8
  from iris_devtester.containers.iris_container import IRISContainer
9
- from .manifest import FixtureManifest, LoadResult, FixtureLoadError, FixtureValidationError
9
+
10
+ from .manifest import FixtureLoadError, FixtureManifest, FixtureValidationError, LoadResult
10
11
 
11
12
  logger = logging.getLogger(__name__)
12
13
 
14
+
13
15
  class DATFixtureLoader:
14
16
 
15
17
  def __init__(self, container: Optional[IRISContainer] = None, **kwargs):
16
18
  self.container = container
17
- self.connection_config = kwargs.get('connection_config')
19
+ self.connection_config = kwargs.get("connection_config")
18
20
  self._owns_container = False
19
21
 
20
- def validate_fixture(self, fixture_path: str, validate_checksum: bool = True) -> FixtureManifest:
21
- """Contract‑compatible validation wrapper."""
22
+ def validate_fixture(
23
+ self, fixture_path: str, validate_checksum: bool = True
24
+ ) -> FixtureManifest:
22
25
  from .validator import FixtureValidator
26
+
23
27
  validator = FixtureValidator()
24
28
  result = validator.validate_fixture(fixture_path, validate_checksum=validate_checksum)
25
29
  if not result.valid or result.manifest is None:
@@ -56,95 +60,232 @@ class DATFixtureLoader:
56
60
 
57
61
  if validate_checksum:
58
62
  from .validator import FixtureValidator
63
+
59
64
  validator = FixtureValidator()
60
65
  validation = validator.validate_fixture(fixture_path)
61
66
  if not validation.valid:
62
- raise FixtureValidationError(
63
- f"Checksum validation failed: {validation.errors}"
64
- )
67
+ raise FixtureValidationError(f"Checksum validation failed: {validation.errors}")
65
68
 
66
69
  container_name = self.container.get_container_name()
67
- container_dat_path = f"/tmp/RESTORE_{namespace}.DAT"
70
+ container_gof_path = f"/tmp/RESTORE_{namespace}.gof"
71
+ container_cls_path = f"/tmp/RESTORE_{namespace}.xml"
68
72
 
69
- subprocess.run([
70
- "docker", "cp", str(dat_file_path),
71
- f"{container_name}:{container_dat_path}"
72
- ], check=True, capture_output=True, text=True)
73
+ # Step 1: Copy fixture files to container
74
+ # The fixture package has globals.gof and optionally classes.xml
75
+ fixture_base = Path(fixture_path)
76
+ gof_file = fixture_base / "globals.gof"
77
+ cls_file = fixture_base / "classes.xml"
73
78
 
74
- db_name = f"DB_{namespace}"
75
- db_dir = f"/usr/irissys/mgr/db_{namespace.lower()}"
76
-
77
- refresh_script = ""
78
- if force_refresh:
79
- refresh_script = f"""
80
- Set nsName = "{namespace}"
81
- Set dbName = "{db_name}"
82
- If ##class(Config.Namespaces).Exists(nsName,.obj) Do ##class(Config.Namespaces).Delete(nsName)
83
- If ##class(Config.Databases).Exists(dbName,.obj) {{
84
- Set dir = obj.Directory
85
- Do ##class(SYS.Database).DismountDatabase(dir)
86
- Do ##class(Config.Databases).Delete(dbName)
87
- }}
88
- """
79
+ if gof_file.exists():
80
+ subprocess.run(
81
+ ["docker", "cp", str(gof_file), f"{container_name}:{container_gof_path}"],
82
+ check=True,
83
+ )
89
84
  else:
90
- refresh_script = f"""
91
- Do ##class(Config.Namespaces).Exists("{namespace}",.obj,.nsStatus)
92
- If nsStatus=1 Write "NAMESPACE_EXISTS","SUCCESS" Halt
93
- """
85
+ raise FixtureLoadError(f"globals.gof not found in {fixture_path}")
86
+
87
+ has_classes = cls_file.exists()
88
+ if has_classes:
89
+ subprocess.run(
90
+ ["docker", "cp", str(cls_file), f"{container_name}:{container_cls_path}"],
91
+ check=True,
92
+ )
93
+
94
+ # Fix permissions on the copied files
95
+ subprocess.run(
96
+ [
97
+ "docker",
98
+ "exec",
99
+ "-u",
100
+ "root",
101
+ container_name,
102
+ "chmod",
103
+ "644",
104
+ container_gof_path,
105
+ ],
106
+ capture_output=True,
107
+ timeout=30,
108
+ )
109
+ if has_classes:
110
+ subprocess.run(
111
+ [
112
+ "docker",
113
+ "exec",
114
+ "-u",
115
+ "root",
116
+ container_name,
117
+ "chmod",
118
+ "644",
119
+ container_cls_path,
120
+ ],
121
+ capture_output=True,
122
+ timeout=30,
123
+ )
94
124
 
95
- objectscript = f"""
125
+ # Step 2: Create namespace if it doesn't exist, then import globals
126
+ # First, ensure the namespace exists (create with default database structure)
127
+ db_dir = f"/usr/irissys/mgr/db_{namespace.lower()}"
128
+ create_ns_script = f"""
129
+ Set ns = "{namespace}"
96
130
  Set dbDir = "{db_dir}"
97
- Set dbName = "{db_name}"
98
-
99
- {refresh_script}
100
-
131
+ If ##class(Config.Namespaces).Exists(ns) Write "NS_READY" Halt
101
132
  If '##class(%File).DirectoryExists(dbDir) Do ##class(%File).CreateDirectoryChain(dbDir)
102
- Do ##class(%File).CopyFile("{container_dat_path}",dbDir_"/IRIS.DAT")
103
-
104
- Set dbProps("Directory") = dbDir
105
- Set status = ##class(Config.Databases).Create(dbName,.dbProps)
106
- If status'=1 Write "DB_CREATE_FAILED" Halt
107
-
108
- Set status = ##class(SYS.Database).MountDatabase(dbDir)
109
-
110
- Set nsProps("Globals") = dbName
111
- Set nsProps("Routines") = dbName
112
- Set status = ##class(Config.Namespaces).Create(nsName,.nsProps)
113
- If status=1 Write "SUCCESS"
133
+ Set db = ##class(SYS.Database).%New()
134
+ Set db.Directory = dbDir
135
+ Set sc = db.%Save()
136
+ If 'sc Write "ERR_DB:",$System.Status.GetErrorText(sc) Halt
137
+ Set sc = ##class(Config.Databases).Create(ns, dbDir)
138
+ If 'sc Write "ERR_DBCFG:",$System.Status.GetErrorText(sc) Halt
139
+ Kill p Set p("Globals") = ns, p("Routines") = ns
140
+ Set sc = ##class(Config.Namespaces).Create(ns, .p)
141
+ If 'sc Write "ERR_NS:",$System.Status.GetErrorText(sc) Halt
142
+ Write "NS_READY"
143
+ Halt
144
+ """
145
+ result = subprocess.run(
146
+ ["docker", "exec", "-i", container_name, "iris", "session", "IRIS", "-U", "%SYS"],
147
+ input=create_ns_script.encode("utf-8"),
148
+ capture_output=True,
149
+ timeout=60,
150
+ )
151
+ stdout = result.stdout.decode("utf-8", errors="replace")
152
+ if "NS_READY" not in stdout:
153
+ raise FixtureLoadError(f"Namespace creation failed: {stdout}")
154
+
155
+ # Step 3a: Import classes FIRST (if available) - this creates SQL table metadata
156
+ if has_classes:
157
+ import_cls_script = f"""
158
+ Set clsFile = "{container_cls_path}"
159
+ Set sc = $SYSTEM.OBJ.Load(clsFile, "ck")
160
+ If 'sc Write "ERR_CLS:",$System.Status.GetErrorText(sc) Halt
161
+ Write "CLASSES_LOADED"
114
162
  Halt
115
- """
163
+ """
164
+ result = subprocess.run(
165
+ [
166
+ "docker",
167
+ "exec",
168
+ "-i",
169
+ container_name,
170
+ "iris",
171
+ "session",
172
+ "IRIS",
173
+ "-U",
174
+ namespace,
175
+ ],
176
+ input=import_cls_script.encode("utf-8"),
177
+ capture_output=True,
178
+ timeout=120,
179
+ )
180
+ stdout = result.stdout.decode("utf-8", errors="replace")
181
+ logger.info(f"Class import output: {stdout}")
182
+ if "CLASSES_LOADED" not in stdout and "ERR_CLS" in stdout:
183
+ raise FixtureLoadError(f"Class import failed: {stdout}")
116
184
 
117
- result = subprocess.run([
118
- "docker", "exec", "-i", container_name,
119
- "iris", "session", "IRIS", "-U", "%SYS"
120
- ], input=objectscript.encode('utf-8'), capture_output=True, timeout=60)
185
+ # Step 3b: Import globals (data)
186
+ # Signature: Import(Nsp, GlobalList, FileName, InputFormat)
187
+ # InputFormat=7 for GOF (block format)
188
+ import_gof_script = f"""
189
+ Set file = "{container_gof_path}"
190
+ Set sc = ##class(%Library.Global).Import($Namespace, "*", file, 7)
191
+ If 'sc Write "ERR_IMPORT:",$System.Status.GetErrorText(sc) Halt
192
+ Write "SUCCESS"
193
+ Halt
194
+ """
195
+ # Run import in the TARGET namespace so globals go to the right place
196
+ result = subprocess.run(
197
+ [
198
+ "docker",
199
+ "exec",
200
+ "-i",
201
+ container_name,
202
+ "iris",
203
+ "session",
204
+ "IRIS",
205
+ "-U",
206
+ namespace,
207
+ ],
208
+ input=import_gof_script.encode("utf-8"),
209
+ capture_output=True,
210
+ timeout=120,
211
+ )
121
212
 
122
- stdout = result.stdout.decode('utf-8', errors='replace')
213
+ stdout = result.stdout.decode("utf-8", errors="replace")
214
+ stderr = result.stderr.decode("utf-8", errors="replace")
215
+ logger.info(f"GOF import output: {stdout}")
216
+ if stderr:
217
+ logger.warning(f"GOF import stderr: {stderr}")
123
218
  if "SUCCESS" not in stdout:
124
219
  raise FixtureLoadError(f"Restore failed: {stdout}")
125
220
 
126
- subprocess.run([
127
- "docker", "exec", "-u", "root", container_name,
128
- "chown", "-R", "irisowner:irisowner", db_dir
129
- ], check=False)
130
-
131
- time.sleep(2)
221
+ # Step 3: CRITICAL - Ensure test credentials still work after restore
222
+ # Restoration can sometimes overwrite security settings or trigger flags.
223
+ ensure_user_script = """
224
+ Set user="testuser",pass="testpassword"
225
+ Kill p
226
+ Set p("PasswordExternal")=pass,p("Roles")="%ALL",p("ChangePassword")=0,p("PasswordNeverExpires")=1
227
+ If ##class(Security.Users).Exists(user) Do ##class(Security.Users).Delete(user)
228
+ Set sc=##class(Security.Users).Create(user,.p)
229
+ Do ##class(Security.Services).Get("%Service_CallIn",.svcP)
230
+ Set svcP("Enabled")=1
231
+ Do ##class(Security.Services).Modify("%Service_CallIn",.svcP)
232
+ If $$$ISERR(sc) Write "ERR:",$System.Status.GetErrorText(sc) Halt
233
+ Write "SUCCESS" Halt
234
+ """
235
+ subprocess.run(
236
+ ["docker", "exec", "-i", container_name, "iris", "session", "IRIS", "-U", "%SYS"],
237
+ input=ensure_user_script.encode("utf-8"),
238
+ capture_output=True,
239
+ timeout=30,
240
+ )
241
+
242
+ # Also unexpire everything else just in case
243
+ from iris_devtester.utils.password import unexpire_all_passwords
244
+
245
+ unexpire_all_passwords(container_name)
246
+
247
+ time.sleep(5) # Give IRIS a moment to stabilize security changes
132
248
 
133
- except TimeoutExpired:
134
- raise FixtureLoadError("Restore timed out")
135
249
  except Exception as e:
136
- if isinstance(e, FixtureLoadError): raise
250
+ if isinstance(e, FixtureLoadError):
251
+ raise
137
252
  raise FixtureLoadError(f"Restore failed: {e}")
138
253
 
254
+ return self._verify_load(namespace, manifest, start_time)
255
+
256
+ def _verify_load(
257
+ self, namespace: str, manifest: FixtureManifest, start_time: float
258
+ ) -> LoadResult:
259
+ if not self.container:
260
+ raise RuntimeError("IRIS container required for verification")
261
+
139
262
  try:
140
- from iris_devtester.connections import get_connection
263
+ # Use the modern connection manager which has automatic password reset remediation
141
264
  from iris_devtester.config import IRISConfig
265
+ from iris_devtester.connections.connection import (
266
+ get_connection as get_modern_connection,
267
+ )
268
+
269
+ # CRITICAL: Use the provided connection_config if available (has verified testuser credentials)
270
+ # Otherwise fall back to container's config
271
+ if self.connection_config:
272
+ config = self.connection_config
273
+ else:
274
+ config = self.container.get_config()
142
275
 
143
- config = self.container.get_config()
144
- conn = get_connection(IRISConfig(
145
- host=config.host, port=config.port,
146
- namespace=namespace, username=config.username, password=config.password
147
- ))
276
+ # Use the actual container name for remediation if needed
277
+ container_name = self.container.get_container_name()
278
+
279
+ conn = get_modern_connection(
280
+ IRISConfig(
281
+ host=config.host,
282
+ port=config.port,
283
+ namespace=namespace,
284
+ username=config.username,
285
+ password=config.password,
286
+ container_name=container_name,
287
+ )
288
+ )
148
289
  cursor = conn.cursor()
149
290
  verified_tables = []
150
291
  for table_info in manifest.tables:
@@ -155,24 +296,26 @@ class DATFixtureLoader:
155
296
  conn.close()
156
297
 
157
298
  return LoadResult(
158
- success=True, manifest=manifest,
159
- namespace=namespace, tables_loaded=verified_tables,
160
- elapsed_seconds=time.time() - start_time
299
+ success=True,
300
+ manifest=manifest,
301
+ namespace=namespace,
302
+ tables_loaded=verified_tables,
303
+ elapsed_seconds=time.time() - start_time,
161
304
  )
162
305
  except Exception as e:
163
306
  raise FixtureLoadError(f"Table verification failed: {e}")
164
307
 
165
308
  def cleanup_fixture(self, namespace: str, delete_namespace: bool = True):
166
- """Contract‑compatible cleanup wrapper."""
167
309
  if not namespace:
168
310
  raise ValueError("Namespace is required")
169
311
  if not self.container:
170
312
  raise RuntimeError("IRIS container required for cleanup")
171
-
172
313
  if delete_namespace:
173
314
  self.container.delete_namespace(namespace)
174
315
 
175
316
  def get_connection(self):
176
317
  """Contract‑compatible connection getter."""
177
- from iris_devtester.connections import get_connection
178
- return get_connection(config=self.connection_config)
318
+ # Use the modern connection manager which has automatic password reset remediation
319
+ from iris_devtester.connections.connection import get_connection as get_modern_connection
320
+
321
+ return get_modern_connection(config=self.connection_config)
@@ -4,35 +4,40 @@ This module defines the data structures for IRIS .DAT fixture manifests,
4
4
  including FixtureManifest, TableInfo, ValidationResult, and LoadResult.
5
5
  """
6
6
 
7
- from dataclasses import dataclass, field, asdict
8
- from typing import List, Dict, Any, Optional
9
7
  import json
8
+ from dataclasses import asdict, dataclass, field
10
9
  from pathlib import Path
10
+ from typing import Any, Dict, List, Optional
11
11
 
12
12
 
13
13
  # Custom Exceptions
14
14
  class FixtureError(Exception):
15
15
  """Base exception for fixture operations."""
16
+
16
17
  pass
17
18
 
18
19
 
19
20
  class FixtureValidationError(FixtureError):
20
21
  """Raised when fixture validation fails."""
22
+
21
23
  pass
22
24
 
23
25
 
24
26
  class FixtureLoadError(FixtureError):
25
27
  """Raised when fixture loading fails."""
28
+
26
29
  pass
27
30
 
28
31
 
29
32
  class FixtureCreateError(FixtureError):
30
33
  """Raised when fixture creation fails."""
34
+
31
35
  pass
32
36
 
33
37
 
34
38
  class ChecksumMismatchError(FixtureValidationError):
35
39
  """Raised when file checksum doesn't match manifest."""
40
+
36
41
  pass
37
42
 
38
43
 
@@ -218,10 +223,7 @@ class FixtureManifest:
218
223
  errors.append("Duplicate table names found")
219
224
 
220
225
  return ValidationResult(
221
- valid=len(errors) == 0,
222
- errors=errors,
223
- warnings=warnings,
224
- manifest=self
226
+ valid=len(errors) == 0, errors=errors, warnings=warnings, manifest=self
225
227
  )
226
228
 
227
229
 
@@ -86,24 +86,26 @@ def export_classes(
86
86
  """
87
87
  qualifiers = "/displaylog" + ("/compile" if compile else "")
88
88
 
89
- objectscript = f'''
89
+ objectscript = f"""
90
90
  ZN "{namespace}"
91
91
  Set sc = $SYSTEM.OBJ.Export("{pattern}", "{output_file}", "{qualifiers}")
92
92
  Write $Select(sc=1:1,1:0)
93
93
  Halt
94
- '''
94
+ """
95
95
 
96
96
  try:
97
97
  container_name = container.get_container_name()
98
98
 
99
99
  cmd = [
100
- "docker", "exec", container_name, "sh", "-c",
101
- f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF'
100
+ "docker",
101
+ "exec",
102
+ container_name,
103
+ "sh",
104
+ "-c",
105
+ f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF',
102
106
  ]
103
107
 
104
- result = subprocess.run(
105
- cmd, capture_output=True, text=True, timeout=120
106
- )
108
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
107
109
 
108
110
  raw_output = result.stdout + result.stderr
109
111
  success = result.returncode == 0 and "1" in result.stdout
@@ -173,24 +175,26 @@ def import_classes(
173
175
  """
174
176
  qualifiers = "/displaylog" + ("/compile" if compile else "")
175
177
 
176
- objectscript = f'''
178
+ objectscript = f"""
177
179
  ZN "{namespace}"
178
180
  Set sc = $SYSTEM.OBJ.Import("{input_file}", "{qualifiers}")
179
181
  Write $Select(sc=1:1,1:0)
180
182
  Halt
181
- '''
183
+ """
182
184
 
183
185
  try:
184
186
  container_name = container.get_container_name()
185
187
 
186
188
  cmd = [
187
- "docker", "exec", container_name, "sh", "-c",
188
- f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF'
189
+ "docker",
190
+ "exec",
191
+ container_name,
192
+ "sh",
193
+ "-c",
194
+ f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF',
189
195
  ]
190
196
 
191
- result = subprocess.run(
192
- cmd, capture_output=True, text=True, timeout=120
193
- )
197
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
194
198
 
195
199
  raw_output = result.stdout + result.stderr
196
200
  success = result.returncode == 0 and "1" in result.stdout
@@ -260,23 +264,25 @@ def export_global(
260
264
  # Normalize global name (remove leading ^ if present for comparison)
261
265
  clean_global = global_name.lstrip("^")
262
266
 
263
- objectscript = f'''
267
+ objectscript = f"""
264
268
  Set sc = ##class(%Library.Global).Export("{namespace}", "^{clean_global}", "{output_file}")
265
269
  Write $Select(sc=1:1,1:0)
266
270
  Halt
267
- '''
271
+ """
268
272
 
269
273
  try:
270
274
  container_name = container.get_container_name()
271
275
 
272
276
  cmd = [
273
- "docker", "exec", container_name, "sh", "-c",
274
- f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF'
277
+ "docker",
278
+ "exec",
279
+ container_name,
280
+ "sh",
281
+ "-c",
282
+ f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF',
275
283
  ]
276
284
 
277
- result = subprocess.run(
278
- cmd, capture_output=True, text=True, timeout=120
279
- )
285
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
280
286
 
281
287
  raw_output = result.stdout + result.stderr
282
288
  success = result.returncode == 0 and "1" in result.stdout
@@ -342,23 +348,25 @@ def import_global(
342
348
  ... if result.success:
343
349
  ... print("Imported global data")
344
350
  """
345
- objectscript = f'''
351
+ objectscript = f"""
346
352
  Set sc = ##class(%Library.Global).Import("{namespace}", "{input_file}")
347
353
  Write $Select(sc=1:1,1:0)
348
354
  Halt
349
- '''
355
+ """
350
356
 
351
357
  try:
352
358
  container_name = container.get_container_name()
353
359
 
354
360
  cmd = [
355
- "docker", "exec", container_name, "sh", "-c",
356
- f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF'
361
+ "docker",
362
+ "exec",
363
+ container_name,
364
+ "sh",
365
+ "-c",
366
+ f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF',
357
367
  ]
358
368
 
359
- result = subprocess.run(
360
- cmd, capture_output=True, text=True, timeout=120
361
- )
369
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
362
370
 
363
371
  raw_output = result.stdout + result.stderr
364
372
  success = result.returncode == 0 and "1" in result.stdout
@@ -422,24 +430,26 @@ def export_package(
422
430
  ... if result.success:
423
431
  ... print(f"Exported package to {result.output_file}")
424
432
  """
425
- objectscript = f'''
433
+ objectscript = f"""
426
434
  ZN "{namespace}"
427
435
  Set sc = $SYSTEM.OBJ.ExportPackage("{package_name}", "{output_file}")
428
436
  Write $Select(sc=1:1,1:0)
429
437
  Halt
430
- '''
438
+ """
431
439
 
432
440
  try:
433
441
  container_name = container.get_container_name()
434
442
 
435
443
  cmd = [
436
- "docker", "exec", container_name, "sh", "-c",
437
- f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF'
444
+ "docker",
445
+ "exec",
446
+ container_name,
447
+ "sh",
448
+ "-c",
449
+ f'iris session IRIS -U %SYS << "EOF"\n{objectscript}\nEOF',
438
450
  ]
439
451
 
440
- result = subprocess.run(
441
- cmd, capture_output=True, text=True, timeout=120
442
- )
452
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
443
453
 
444
454
  raw_output = result.stdout + result.stderr
445
455
  success = result.returncode == 0 and "1" in result.stdout
@@ -9,10 +9,10 @@ from pathlib import Path
9
9
  from typing import Optional
10
10
 
11
11
  from .manifest import (
12
+ ChecksumMismatchError,
12
13
  FixtureManifest,
13
- ValidationResult,
14
14
  FixtureValidationError,
15
- ChecksumMismatchError,
15
+ ValidationResult,
16
16
  )
17
17
 
18
18
 
@@ -104,8 +104,7 @@ class FixtureValidator:
104
104
  """
105
105
  if not expected_checksum.startswith("sha256:"):
106
106
  raise ValueError(
107
- f"Invalid checksum format: {expected_checksum}. "
108
- "Must start with 'sha256:'"
107
+ f"Invalid checksum format: {expected_checksum}. " "Must start with 'sha256:'"
109
108
  )
110
109
 
111
110
  actual_checksum = self.calculate_sha256(file_path, chunk_size)
@@ -212,9 +211,7 @@ class FixtureValidator:
212
211
  # Validate checksum if requested
213
212
  if validate_checksum:
214
213
  try:
215
- self.validate_checksum(
216
- str(dat_file), manifest.checksum, chunk_size
217
- )
214
+ self.validate_checksum(str(dat_file), manifest.checksum, chunk_size)
218
215
  except ChecksumMismatchError:
219
216
  # Re-raise ChecksumMismatchError - it's a critical failure
220
217
  # that requires immediate attention (Constitutional Principle #5)
@@ -178,9 +178,7 @@ class LangChainIRISContainer(IRISContainer):
178
178
  password = getattr(self, "password", "SYS")
179
179
  namespace = getattr(self, "namespace", "USER")
180
180
 
181
- connection_string = (
182
- f"iris://{username}:{password}@{host}:{port}/{namespace}"
183
- )
181
+ connection_string = f"iris://{username}:{password}@{host}:{port}/{namespace}"
184
182
 
185
183
  return connection_string
186
184
 
@@ -247,9 +245,7 @@ class LangChainIRISContainer(IRISContainer):
247
245
 
248
246
  connection_string = self.get_connection_string()
249
247
 
250
- logger.info(
251
- f"Creating LangChain chat history for session '{session_id}'"
252
- )
248
+ logger.info(f"Creating LangChain chat history for session '{session_id}'")
253
249
 
254
250
  history = IRISChatMessageHistory(
255
251
  connection_string=connection_string,
@@ -192,7 +192,10 @@ class PortRegistry:
192
192
  # Find stale assignments (container doesn't exist)
193
193
  active_assignments = []
194
194
  for assignment in assignments:
195
- if assignment.container_name and assignment.container_name not in container_names:
195
+ if (
196
+ assignment.container_name
197
+ and assignment.container_name not in container_names
198
+ ):
196
199
  # Container removed - mark as stale
197
200
  assignment.status = "stale"
198
201
  released.append(assignment)
@@ -298,9 +301,7 @@ class PortRegistry:
298
301
  return port
299
302
 
300
303
  # All ports exhausted
301
- raise PortExhaustedError(
302
- port_range=self.port_range, current_assignments=assignments
303
- )
304
+ raise PortExhaustedError(port_range=self.port_range, current_assignments=assignments)
304
305
 
305
306
  def _get_docker_bound_ports(self) -> set:
306
307
  """