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
@@ -9,24 +9,24 @@ Provides 5 commands:
9
9
  - fixture info: Show detailed fixture information
10
10
  """
11
11
 
12
- import click
13
12
  import sys
14
13
  from pathlib import Path
15
14
  from typing import Optional
16
15
 
16
+ import click
17
+
18
+ # Import IRISContainer for container handling
19
+ from iris_devtester.containers import IRISContainer
17
20
  from iris_devtester.fixtures import (
18
- FixtureCreator,
19
21
  DATFixtureLoader,
20
- FixtureValidator,
21
- FixtureManifest,
22
22
  FixtureCreateError,
23
+ FixtureCreator,
23
24
  FixtureLoadError,
25
+ FixtureManifest,
24
26
  FixtureValidationError,
27
+ FixtureValidator,
25
28
  )
26
29
 
27
- # Import IRISContainer for container handling
28
- from iris_devtester.containers import IRISContainer
29
-
30
30
 
31
31
  @click.group()
32
32
  def fixture():
@@ -35,14 +35,24 @@ def fixture():
35
35
 
36
36
 
37
37
  @fixture.command()
38
- @click.option('--name', required=True, help='Fixture identifier (e.g., "test-entities-100")')
39
- @click.option('--namespace', required=True, help='Source namespace to export (e.g., "USER_TEST_100")')
40
- @click.option('--output', required=True, help='Output directory for fixture')
41
- @click.option('--description', default="", help='Human-readable description')
42
- @click.option('--version', default="1.0.0", help='Semantic version')
43
- @click.option('--container', default=None, help='IRIS container name to use for fixture creation')
44
- @click.option('--verbose', is_flag=True, help='Show detailed progress')
45
- def create(name: str, namespace: str, output: str, description: str, version: str, container: str, verbose: bool):
38
+ @click.option("--name", required=True, help='Fixture identifier (e.g., "test-entities-100")')
39
+ @click.option(
40
+ "--namespace", required=True, help='Source namespace to export (e.g., "USER_TEST_100")'
41
+ )
42
+ @click.option("--output", required=True, help="Output directory for fixture")
43
+ @click.option("--description", default="", help="Human-readable description")
44
+ @click.option("--version", default="1.0.0", help="Semantic version")
45
+ @click.option("--container", default=None, help="IRIS container name to use for fixture creation")
46
+ @click.option("--verbose", is_flag=True, help="Show detailed progress")
47
+ def create(
48
+ name: str,
49
+ namespace: str,
50
+ output: str,
51
+ description: str,
52
+ version: str,
53
+ container: str,
54
+ verbose: bool,
55
+ ):
46
56
  """Create .DAT fixture by exporting IRIS namespace."""
47
57
  try:
48
58
  if verbose:
@@ -54,7 +64,9 @@ def create(name: str, namespace: str, output: str, description: str, version: st
54
64
  # Attach to an existing container by name
55
65
  container_obj = IRISContainer.attach(container)
56
66
  except Exception as e:
57
- click.secho(f"\n❌ Failed to attach to container '{container}': {e}", fg='red', bold=True)
67
+ click.secho(
68
+ f"\n❌ Failed to attach to container '{container}': {e}", fg="red", bold=True
69
+ )
58
70
  sys.exit(1)
59
71
  else:
60
72
  # No container specified; start a temporary community container
@@ -63,7 +75,9 @@ def create(name: str, namespace: str, output: str, description: str, version: st
63
75
  # Ensure it's started
64
76
  container_obj.start()
65
77
  except Exception as e:
66
- click.secho(f"\n❌ Failed to start community IRIS container: {e}", fg='red', bold=True)
78
+ click.secho(
79
+ f"\n❌ Failed to start community IRIS container: {e}", fg="red", bold=True
80
+ )
67
81
  sys.exit(1)
68
82
  creator = FixtureCreator(container=container_obj)
69
83
 
@@ -75,7 +89,7 @@ def create(name: str, namespace: str, output: str, description: str, version: st
75
89
  namespace=namespace,
76
90
  output_dir=output,
77
91
  description=description,
78
- version=version
92
+ version=version,
79
93
  )
80
94
 
81
95
  if verbose:
@@ -89,7 +103,7 @@ def create(name: str, namespace: str, output: str, description: str, version: st
89
103
  table_count = len(manifest.tables)
90
104
  total_rows = sum(t.row_count for t in manifest.tables)
91
105
 
92
- click.secho(f"\n✅ Fixture created: {manifest.fixture_id}", fg='green', bold=True)
106
+ click.secho(f"\n✅ Fixture created: {manifest.fixture_id}", fg="green", bold=True)
93
107
  click.echo(f"\nLocation: {output}")
94
108
  click.echo(f"Tables: {table_count}")
95
109
  click.echo(f"Total rows: {total_rows:,}")
@@ -103,35 +117,38 @@ def create(name: str, namespace: str, output: str, description: str, version: st
103
117
  sys.exit(0)
104
118
 
105
119
  except FileExistsError as e:
106
- click.secho(f"\n❌ Failed to create fixture", fg='red', bold=True)
120
+ click.secho(f"\n❌ Failed to create fixture", fg="red", bold=True)
107
121
  click.echo(f"\n{str(e)}")
108
122
  sys.exit(1)
109
123
 
110
124
  except FixtureCreateError as e:
111
- click.secho(f"\n❌ Failed to create fixture", fg='red', bold=True)
125
+ click.secho(f"\n❌ Failed to create fixture", fg="red", bold=True)
112
126
  click.echo(f"\n{str(e)}")
113
127
  sys.exit(3)
114
128
 
115
129
  except ConnectionError as e:
116
- click.secho(f"\n❌ Failed to create fixture", fg='red', bold=True)
130
+ click.secho(f"\n❌ Failed to create fixture", fg="red", bold=True)
117
131
  click.echo(f"\nConnection error: {str(e)}")
118
132
  sys.exit(4)
119
133
 
120
134
  except Exception as e:
121
- click.secho(f"\n❌ Failed to create fixture", fg='red', bold=True)
135
+ click.secho(f"\n❌ Failed to create fixture", fg="red", bold=True)
122
136
  click.echo(f"\nUnexpected error: {str(e)}")
123
137
  if verbose:
124
138
  import traceback
139
+
125
140
  traceback.print_exc()
126
141
  sys.exit(1)
127
142
 
128
143
 
129
144
  @fixture.command()
130
- @click.option('--fixture', required=True, help='Path to fixture directory')
131
- @click.option('--namespace', default=None, help='Target namespace (default: use manifest namespace)')
132
- @click.option('--no-validate', is_flag=True, help='Skip checksum validation (faster, less safe)')
133
- @click.option('--force', is_flag=True, help='Force reload by deleting existing namespace')
134
- @click.option('--verbose', is_flag=True, help='Show detailed progress')
145
+ @click.option("--fixture", required=True, help="Path to fixture directory")
146
+ @click.option(
147
+ "--namespace", default=None, help="Target namespace (default: use manifest namespace)"
148
+ )
149
+ @click.option("--no-validate", is_flag=True, help="Skip checksum validation (faster, less safe)")
150
+ @click.option("--force", is_flag=True, help="Force reload by deleting existing namespace")
151
+ @click.option("--verbose", is_flag=True, help="Show detailed progress")
135
152
  def load(fixture: str, namespace: Optional[str], no_validate: bool, force: bool, verbose: bool):
136
153
  """Load .DAT fixture into IRIS database."""
137
154
  try:
@@ -152,14 +169,16 @@ def load(fixture: str, namespace: Optional[str], no_validate: bool, force: bool,
152
169
  fixture_path=fixture,
153
170
  target_namespace=namespace,
154
171
  validate_checksum=validate_checksum,
155
- force_refresh=force
172
+ force_refresh=force,
156
173
  )
157
174
 
158
175
  # Calculate totals
159
176
  table_count = len(result.tables_loaded)
160
- total_rows = sum(t.row_count for t in result.manifest.tables if t.name in result.tables_loaded)
177
+ total_rows = sum(
178
+ t.row_count for t in result.manifest.tables if t.name in result.tables_loaded
179
+ )
161
180
 
162
- click.secho(f"\n✅ Fixture loaded: {result.manifest.fixture_id}", fg='green', bold=True)
181
+ click.secho(f"\n✅ Fixture loaded: {result.manifest.fixture_id}", fg="green", bold=True)
163
182
  click.echo(f"\nNamespace: {result.namespace}")
164
183
  click.echo(f"Tables loaded: {table_count}")
165
184
  click.echo(f"Total rows: {total_rows:,}")
@@ -178,40 +197,41 @@ def load(fixture: str, namespace: Optional[str], no_validate: bool, force: bool,
178
197
  sys.exit(0)
179
198
 
180
199
  except FileNotFoundError as e:
181
- click.secho(f"\n❌ Failed to load fixture", fg='red', bold=True)
200
+ click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
182
201
  click.echo(f"\nFixture not found: {str(e)}")
183
202
  sys.exit(1)
184
203
 
185
204
  except FixtureValidationError as e:
186
- click.secho(f"\n❌ Failed to load fixture", fg='red', bold=True)
205
+ click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
187
206
  click.echo(f"\n{str(e)}")
188
207
  sys.exit(2)
189
208
 
190
209
  except FixtureLoadError as e:
191
- click.secho(f"\n❌ Failed to load fixture", fg='red', bold=True)
210
+ click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
192
211
  click.echo(f"\n{str(e)}")
193
212
  click.echo("\nNote: Namespace mount is atomic (all-or-nothing operation)")
194
213
  sys.exit(4)
195
214
 
196
215
  except ConnectionError as e:
197
- click.secho(f"\n❌ Failed to load fixture", fg='red', bold=True)
216
+ click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
198
217
  click.echo(f"\nConnection error: {str(e)}")
199
218
  sys.exit(5)
200
219
 
201
220
  except Exception as e:
202
- click.secho(f"\n❌ Failed to load fixture", fg='red', bold=True)
221
+ click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
203
222
  click.echo(f"\nUnexpected error: {str(e)}")
204
223
  if verbose:
205
224
  import traceback
225
+
206
226
  traceback.print_exc()
207
227
  sys.exit(1)
208
228
 
209
229
 
210
230
  @fixture.command()
211
- @click.option('--fixture', required=True, help='Path to fixture directory')
212
- @click.option('--no-checksums', is_flag=True, help='Skip checksum validation (faster)')
213
- @click.option('--recalc', is_flag=True, help='Recalculate checksums and update manifest')
214
- @click.option('--verbose', is_flag=True, help='Show detailed validation results')
231
+ @click.option("--fixture", required=True, help="Path to fixture directory")
232
+ @click.option("--no-checksums", is_flag=True, help="Skip checksum validation (faster)")
233
+ @click.option("--recalc", is_flag=True, help="Recalculate checksums and update manifest")
234
+ @click.option("--verbose", is_flag=True, help="Show detailed validation results")
215
235
  def validate(fixture: str, no_checksums: bool, recalc: bool, verbose: bool):
216
236
  """Validate fixture integrity (manifest, files, checksums)."""
217
237
  try:
@@ -222,7 +242,7 @@ def validate(fixture: str, no_checksums: bool, recalc: bool, verbose: bool):
222
242
  click.echo("Recalculating checksums...")
223
243
 
224
244
  manifest = validator.recalculate_checksums(fixture)
225
- click.secho(f"\n✅ Checksums recalculated", fg='green', bold=True)
245
+ click.secho(f"\n✅ Checksums recalculated", fg="green", bold=True)
226
246
  click.echo(f"\nNew checksum: {manifest.checksum}")
227
247
  sys.exit(0)
228
248
 
@@ -238,14 +258,14 @@ def validate(fixture: str, no_checksums: bool, recalc: bool, verbose: bool):
238
258
  if result.valid:
239
259
  manifest = result.manifest
240
260
  if manifest is None:
241
- click.secho("\n❌ Unexpected error: manifest is None", fg='red', bold=True)
261
+ click.secho("\n❌ Unexpected error: manifest is None", fg="red", bold=True)
242
262
  sys.exit(1)
243
263
  sizes = validator.get_fixture_size(fixture)
244
264
 
245
265
  table_count = len(manifest.tables)
246
266
  total_rows = sum(t.row_count for t in manifest.tables)
247
267
 
248
- click.secho(f"\n✅ Fixture is valid: {manifest.fixture_id}", fg='green', bold=True)
268
+ click.secho(f"\n✅ Fixture is valid: {manifest.fixture_id}", fg="green", bold=True)
249
269
  click.echo(f"\nFixture: {manifest.fixture_id}")
250
270
  click.echo(f"Version: {manifest.version}")
251
271
  click.echo(f"Schema: {manifest.schema_version}")
@@ -259,58 +279,61 @@ def validate(fixture: str, no_checksums: bool, recalc: bool, verbose: bool):
259
279
  click.echo(f" - {table}")
260
280
 
261
281
  if result.warnings:
262
- click.secho(f"\nWarnings ({len(result.warnings)}):", fg='yellow')
282
+ click.secho(f"\nWarnings ({len(result.warnings)}):", fg="yellow")
263
283
  for warning in result.warnings:
264
284
  click.echo(f" - {warning}")
265
285
 
266
286
  sys.exit(0)
267
287
 
268
288
  else:
269
- click.secho(f"\n❌ Fixture validation failed", fg='red', bold=True)
289
+ click.secho(f"\n❌ Fixture validation failed", fg="red", bold=True)
270
290
  click.echo(f"\nErrors ({len(result.errors)}):")
271
291
  for error in result.errors:
272
292
  click.echo(f" - {error}")
273
293
 
274
294
  if result.warnings:
275
- click.secho(f"\nWarnings ({len(result.warnings)}):", fg='yellow')
295
+ click.secho(f"\nWarnings ({len(result.warnings)}):", fg="yellow")
276
296
  for warning in result.warnings:
277
297
  click.echo(f" - {warning}")
278
298
 
279
299
  sys.exit(1)
280
300
 
281
301
  except FileNotFoundError as e:
282
- click.secho(f"\n❌ Fixture not found", fg='red', bold=True)
302
+ click.secho(f"\n❌ Fixture not found", fg="red", bold=True)
283
303
  click.echo(f"\n{str(e)}")
284
304
  sys.exit(2)
285
305
 
286
306
  except Exception as e:
287
- click.secho(f"\n❌ Validation failed", fg='red', bold=True)
307
+ click.secho(f"\n❌ Validation failed", fg="red", bold=True)
288
308
  click.echo(f"\nUnexpected error: {str(e)}")
289
309
  if verbose:
290
310
  import traceback
311
+
291
312
  traceback.print_exc()
292
313
  sys.exit(1)
293
314
 
294
315
 
295
316
  @fixture.command()
296
- @click.argument('path', default="./fixtures")
297
- @click.option('--verbose', is_flag=True, help='Show detailed fixture info')
317
+ @click.argument("path", default="./fixtures")
318
+ @click.option("--verbose", is_flag=True, help="Show detailed fixture info")
298
319
  def list(path: str, verbose: bool):
299
320
  """List available fixtures in directory."""
300
321
  try:
301
322
  fixtures_dir = Path(path)
302
323
 
303
324
  if not fixtures_dir.exists():
304
- click.secho(f"\n❌ Directory not found: {path}", fg='red')
325
+ click.secho(f"\n❌ Directory not found: {path}", fg="red")
305
326
  sys.exit(1)
306
327
 
307
328
  # Find all manifest.json files
308
329
  manifests = list(fixtures_dir.rglob("manifest.json"))
309
330
 
310
331
  if not manifests:
311
- click.secho(f"\nNo fixtures found in {path}", fg='yellow')
332
+ click.secho(f"\nNo fixtures found in {path}", fg="yellow")
312
333
  click.echo(f"\nTo create a fixture:")
313
- click.echo(f" iris-devtester fixture create --name my-fixture --namespace USER --output {path}/my-fixture")
334
+ click.echo(
335
+ f" iris-devtester fixture create --name my-fixture --namespace USER --output {path}/my-fixture"
336
+ )
314
337
  sys.exit(0)
315
338
 
316
339
  # Load and display fixtures
@@ -323,13 +346,11 @@ def list(path: str, verbose: bool):
323
346
  manifest = FixtureManifest.from_file(str(manifest_file))
324
347
  fixture_dir = manifest_file.parent
325
348
  sizes = validator.get_fixture_size(str(fixture_dir))
326
- total_size += sizes['total_bytes']
349
+ total_size += sizes["total_bytes"]
327
350
 
328
- fixtures.append({
329
- 'manifest': manifest,
330
- 'path': fixture_dir,
331
- 'size_mb': sizes['total_mb']
332
- })
351
+ fixtures.append(
352
+ {"manifest": manifest, "path": fixture_dir, "size_mb": sizes["total_mb"]}
353
+ )
333
354
  except Exception:
334
355
  # Skip invalid fixtures
335
356
  continue
@@ -337,13 +358,15 @@ def list(path: str, verbose: bool):
337
358
  click.echo(f"\nAvailable fixtures in {path}:\n")
338
359
 
339
360
  for f in fixtures:
340
- manifest = f['manifest']
361
+ manifest = f["manifest"]
341
362
  table_count = len(manifest.tables)
342
363
  total_rows = sum(t.row_count for t in manifest.tables)
343
364
 
344
- click.secho(f" {manifest.fixture_id}", fg='cyan', bold=True)
365
+ click.secho(f" {manifest.fixture_id}", fg="cyan", bold=True)
345
366
  click.echo(f" Version: {manifest.version}")
346
- click.echo(f" Tables: {table_count}, Rows: {total_rows:,}, Size: {f['size_mb']:.2f} MB")
367
+ click.echo(
368
+ f" Tables: {table_count}, Rows: {total_rows:,}, Size: {f['size_mb']:.2f} MB"
369
+ )
347
370
  if manifest.description:
348
371
  click.echo(f" Description: {manifest.description}")
349
372
  click.echo(f" Path: {f['path']}")
@@ -362,14 +385,14 @@ def list(path: str, verbose: bool):
362
385
  sys.exit(0)
363
386
 
364
387
  except Exception as e:
365
- click.secho(f"\n❌ Failed to list fixtures", fg='red', bold=True)
388
+ click.secho(f"\n❌ Failed to list fixtures", fg="red", bold=True)
366
389
  click.echo(f"\nError: {str(e)}")
367
390
  sys.exit(1)
368
391
 
369
392
 
370
393
  @fixture.command()
371
- @click.option('--fixture', required=True, help='Path to fixture directory')
372
- @click.option('--json', 'output_json', is_flag=True, help='Output as JSON')
394
+ @click.option("--fixture", required=True, help="Path to fixture directory")
395
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
373
396
  def info(fixture: str, output_json: bool):
374
397
  """Show detailed information about fixture."""
375
398
  try:
@@ -377,7 +400,7 @@ def info(fixture: str, output_json: bool):
377
400
  manifest_file = Path(fixture) / "manifest.json"
378
401
 
379
402
  if not manifest_file.exists():
380
- click.secho(f"\n❌ Fixture not found: {fixture}", fg='red')
403
+ click.secho(f"\n❌ Fixture not found: {fixture}", fg="red")
381
404
  sys.exit(1)
382
405
 
383
406
  manifest = FixtureManifest.from_file(str(manifest_file))
@@ -385,6 +408,7 @@ def info(fixture: str, output_json: bool):
385
408
 
386
409
  if output_json:
387
410
  import json
411
+
388
412
  data = {
389
413
  "fixture_id": manifest.fixture_id,
390
414
  "version": manifest.version,
@@ -395,13 +419,13 @@ def info(fixture: str, output_json: bool):
395
419
  "namespace": manifest.namespace,
396
420
  "tables": [{"name": t.name, "row_count": t.row_count} for t in manifest.tables],
397
421
  "size": {
398
- "total_mb": sizes['total_mb'],
399
- "manifest_kb": sizes['manifest_kb'],
400
- "dat_mb": sizes['dat_mb']
422
+ "total_mb": sizes["total_mb"],
423
+ "manifest_kb": sizes["manifest_kb"],
424
+ "dat_mb": sizes["dat_mb"],
401
425
  },
402
426
  "features": manifest.features,
403
427
  "known_queries": manifest.known_queries,
404
- "location": str(Path(fixture).resolve())
428
+ "location": str(Path(fixture).resolve()),
405
429
  }
406
430
  click.echo(json.dumps(data, indent=2))
407
431
  else:
@@ -442,15 +466,15 @@ def info(fixture: str, output_json: bool):
442
466
  sys.exit(0)
443
467
 
444
468
  except FileNotFoundError as e:
445
- click.secho(f"\n❌ Fixture not found", fg='red')
469
+ click.secho(f"\n❌ Fixture not found", fg="red")
446
470
  click.echo(f"\n{str(e)}")
447
471
  sys.exit(1)
448
472
 
449
473
  except Exception as e:
450
- click.secho(f"\n❌ Failed to show fixture info", fg='red', bold=True)
474
+ click.secho(f"\n❌ Failed to show fixture info", fg="red", bold=True)
451
475
  click.echo(f"\nError: {str(e)}")
452
476
  sys.exit(1)
453
477
 
454
478
 
455
- if __name__ == '__main__':
479
+ if __name__ == "__main__":
456
480
  fixture()
@@ -13,12 +13,12 @@ import logging
13
13
  import re
14
14
  import subprocess
15
15
  import sys
16
- from typing import Optional, Dict, Any
16
+ from typing import Any, Dict, List, Optional
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
20
20
 
21
- def discover_iris_port(test_ports: list[int] = None) -> Optional[int]:
21
+ def discover_iris_port(test_ports: Optional[List[int]] = None) -> Optional[int]:
22
22
  """
23
23
  Auto-discover IRIS SuperServer port from running instances.
24
24
 
@@ -201,9 +201,7 @@ def discover_native_iris() -> Optional[Dict[str, Any]]:
201
201
  """
202
202
  try:
203
203
  # Run 'iris list' to see running instances
204
- result = subprocess.run(
205
- ["iris", "list"], capture_output=True, text=True, timeout=5
206
- )
204
+ result = subprocess.run(["iris", "list"], capture_output=True, text=True, timeout=5)
207
205
 
208
206
  if result.returncode != 0:
209
207
  logger.debug(f"'iris list' failed with exit code {result.returncode}")
@@ -224,9 +222,7 @@ def discover_native_iris() -> Optional[Dict[str, Any]]:
224
222
  if match:
225
223
  port = int(match.group(1))
226
224
 
227
- logger.info(
228
- f"✅ Discovered native IRIS on SuperServer port {port}"
229
- )
225
+ logger.info(f"✅ Discovered native IRIS on SuperServer port {port}")
230
226
 
231
227
  return {
232
228
  "host": "localhost",
@@ -280,17 +276,13 @@ def auto_discover_iris() -> Optional[Dict[str, Any]]:
280
276
  # Priority 1: Docker containers
281
277
  config = discover_docker_iris()
282
278
  if config:
283
- logger.info(
284
- f"✓ Auto-discovery successful (Docker): {config['host']}:{config['port']}"
285
- )
279
+ logger.info(f"✓ Auto-discovery successful (Docker): {config['host']}:{config['port']}")
286
280
  return config
287
281
 
288
282
  # Priority 2: Native IRIS
289
283
  config = discover_native_iris()
290
284
  if config:
291
- logger.info(
292
- f"✓ Auto-discovery successful (Native): {config['host']}:{config['port']}"
293
- )
285
+ logger.info(f"✓ Auto-discovery successful (Native): {config['host']}:{config['port']}")
294
286
  return config
295
287
 
296
288
  # Priority 3: Multi-port scanning
@@ -303,9 +295,7 @@ def auto_discover_iris() -> Optional[Dict[str, Any]]:
303
295
  "password": "SYS",
304
296
  "namespace": "USER",
305
297
  }
306
- logger.info(
307
- f"✓ Auto-discovery successful (Port scan): {config['host']}:{config['port']}"
308
- )
298
+ logger.info(f"✓ Auto-discovery successful (Port scan): {config['host']}:{config['port']}")
309
299
  return config
310
300
 
311
301
  logger.warning(
@@ -326,9 +316,7 @@ def auto_discover_iris() -> Optional[Dict[str, Any]]:
326
316
 
327
317
  if __name__ == "__main__":
328
318
  """Quick test of auto-discovery."""
329
- logging.basicConfig(
330
- level=logging.INFO, format="%(levelname)s: %(message)s"
331
- )
319
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
332
320
 
333
321
  print("=" * 60)
334
322
  print("IRIS Auto-Discovery Test")
@@ -52,55 +52,33 @@ class ContainerConfig(BaseModel):
52
52
  """
53
53
 
54
54
  edition: Literal["community", "enterprise"] = Field(
55
- default="community",
56
- description="IRIS edition to use"
55
+ default="community", description="IRIS edition to use"
57
56
  )
58
57
  container_name: str = Field(
59
58
  default="iris_db",
60
59
  pattern=r"^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
61
- description="Container name identifier"
60
+ description="Container name identifier",
62
61
  )
63
62
  superserver_port: int = Field(
64
- default=1972,
65
- ge=1024,
66
- le=65535,
67
- description="SuperServer port mapping"
63
+ default=1972, ge=1024, le=65535, description="SuperServer port mapping"
68
64
  )
69
65
  webserver_port: int = Field(
70
- default=52773,
71
- ge=1024,
72
- le=65535,
73
- description="Management Portal port mapping"
66
+ default=52773, ge=1024, le=65535, description="Management Portal port mapping"
74
67
  )
75
68
  namespace: str = Field(
76
- default="USER",
77
- pattern=r"^[A-Z][A-Z0-9%]*$",
78
- description="Default IRIS namespace"
79
- )
80
- password: str = Field(
81
- default="SYS",
82
- min_length=1,
83
- description="_SYSTEM user password"
69
+ default="USER", pattern=r"^[A-Z][A-Z0-9%]*$", description="Default IRIS namespace"
84
70
  )
71
+ password: str = Field(default="SYS", min_length=1, description="_SYSTEM user password")
85
72
  license_key: Optional[str] = Field(
86
- default=None,
87
- description="License key for Enterprise edition"
88
- )
89
- volumes: List[str] = Field(
90
- default_factory=list,
91
- description="Volume mount strings"
73
+ default=None, description="License key for Enterprise edition"
92
74
  )
75
+ volumes: List[str] = Field(default_factory=list, description="Volume mount strings")
93
76
  image: Optional[str] = Field(
94
- default=None,
95
- description="Full Docker image name (overrides edition/image_tag)"
96
- )
97
- image_tag: str = Field(
98
- default="latest",
99
- description="Docker image tag"
77
+ default=None, description="Full Docker image name (overrides edition/image_tag)"
100
78
  )
79
+ image_tag: str = Field(default="latest", description="Docker image tag")
101
80
  cpf_merge: Optional[str] = Field(
102
- default=None,
103
- description="Path to CPF merge file or raw CPF content"
81
+ default=None, description="Path to CPF merge file or raw CPF content"
104
82
  )
105
83
 
106
84
  @field_validator("container_name")
@@ -170,7 +148,17 @@ class ContainerConfig(BaseModel):
170
148
  config_data = {}
171
149
 
172
150
  # Map direct fields
173
- for field in ["edition", "container_name", "namespace", "password", "license_key", "volumes", "image", "image_tag", "cpf_merge"]:
151
+ for field in [
152
+ "edition",
153
+ "container_name",
154
+ "namespace",
155
+ "password",
156
+ "license_key",
157
+ "volumes",
158
+ "image",
159
+ "image_tag",
160
+ "cpf_merge",
161
+ ]:
174
162
  if field in yaml_data:
175
163
  config_data[field] = yaml_data[field]
176
164
 
@@ -304,6 +292,7 @@ class ContainerConfig(BaseModel):
304
292
 
305
293
  class Config:
306
294
  """Pydantic model configuration."""
295
+
307
296
  json_schema_extra = {
308
297
  "example": {
309
298
  "edition": "community",
@@ -314,6 +303,6 @@ class ContainerConfig(BaseModel):
314
303
  "password": "SYS",
315
304
  "license_key": None,
316
305
  "volumes": ["./data:/external"],
317
- "image_tag": "latest"
306
+ "image_tag": "latest",
318
307
  }
319
308
  }