iris-devtester 1.10.0__py3-none-any.whl → 1.10.2__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.
- iris_devtester/__init__.py +1 -1
- iris_devtester/cli/__init__.py +32 -3
- iris_devtester/cli/container.py +37 -18
- iris_devtester/containers/iris_container.py +161 -11
- iris_devtester/fixtures/__init__.py +21 -10
- iris_devtester/fixtures/creator.py +18 -3
- iris_devtester/fixtures/loader.py +5 -2
- iris_devtester/fixtures/manifest.py +13 -4
- iris_devtester/fixtures/validator.py +9 -3
- {iris_devtester-1.10.0.dist-info → iris_devtester-1.10.2.dist-info}/METADATA +1 -1
- {iris_devtester-1.10.0.dist-info → iris_devtester-1.10.2.dist-info}/RECORD +15 -15
- {iris_devtester-1.10.0.dist-info → iris_devtester-1.10.2.dist-info}/WHEEL +0 -0
- {iris_devtester-1.10.0.dist-info → iris_devtester-1.10.2.dist-info}/entry_points.txt +0 -0
- {iris_devtester-1.10.0.dist-info → iris_devtester-1.10.2.dist-info}/licenses/LICENSE +0 -0
- {iris_devtester-1.10.0.dist-info → iris_devtester-1.10.2.dist-info}/top_level.txt +0 -0
iris_devtester/__init__.py
CHANGED
iris_devtester/cli/__init__.py
CHANGED
|
@@ -13,9 +13,38 @@ from .fixture_commands import fixture
|
|
|
13
13
|
@click.version_option(version=__version__, prog_name="iris-devtester")
|
|
14
14
|
def main():
|
|
15
15
|
"""
|
|
16
|
-
iris-devtester -
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
iris-devtester - Python testing toolkit for InterSystems IRIS databases.
|
|
17
|
+
|
|
18
|
+
\b
|
|
19
|
+
WHAT THIS TOOL DOES:
|
|
20
|
+
Manages IRIS database containers, fixtures, and connections for testing.
|
|
21
|
+
Designed for CI/CD pipelines, local development, and AI agent automation.
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
CONTAINER EDITIONS:
|
|
25
|
+
community Full IRIS Community (~3.5GB) - default, all features
|
|
26
|
+
light Minimal for CI/CD (~580MB) - SQL/DBAPI only, 6x smaller
|
|
27
|
+
enterprise Licensed IRIS - requires iris.key file
|
|
28
|
+
|
|
29
|
+
\b
|
|
30
|
+
QUICK START:
|
|
31
|
+
iris-devtester container up # Start community
|
|
32
|
+
iris-devtester container up --edition light # Start light (CI/CD)
|
|
33
|
+
iris-devtester container list # List containers
|
|
34
|
+
iris-devtester test-connection # Verify connectivity
|
|
35
|
+
iris-devtester container status # Check health
|
|
36
|
+
|
|
37
|
+
\b
|
|
38
|
+
COMMON WORKFLOWS:
|
|
39
|
+
Local dev: container up → test-connection → (your tests)
|
|
40
|
+
CI/CD: container up --edition light → test-connection → pytest
|
|
41
|
+
Fixtures: fixture load --fixture ./data → (verify data)
|
|
42
|
+
|
|
43
|
+
\b
|
|
44
|
+
FOR AI AGENTS:
|
|
45
|
+
All commands support --help for detailed options. Commands return
|
|
46
|
+
structured exit codes (0=success, 1=error, 2=not found, 5=timeout).
|
|
47
|
+
Use 'container list --format json' for machine-readable output.
|
|
19
48
|
"""
|
|
20
49
|
pass
|
|
21
50
|
|
iris_devtester/cli/container.py
CHANGED
|
@@ -21,8 +21,23 @@ def container_group(ctx):
|
|
|
21
21
|
"""
|
|
22
22
|
Container lifecycle management commands.
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
\b
|
|
25
|
+
Manage IRIS database containers for testing and development.
|
|
26
|
+
Supports Community, Enterprise, and Light editions.
|
|
27
|
+
|
|
28
|
+
\b
|
|
29
|
+
EDITIONS:
|
|
30
|
+
community Full IRIS Community Edition (~3.5GB, all features)
|
|
31
|
+
light Minimal CI/CD image (~580MB, SQL/DBAPI only)
|
|
32
|
+
enterprise Licensed IRIS (requires --license iris.key)
|
|
33
|
+
|
|
34
|
+
\b
|
|
35
|
+
QUICK START:
|
|
36
|
+
up Create and start a container
|
|
37
|
+
list Show running containers
|
|
38
|
+
status Check container health
|
|
39
|
+
test-connection Verify database connectivity
|
|
40
|
+
stop/remove Clean up containers
|
|
26
41
|
"""
|
|
27
42
|
pass
|
|
28
43
|
|
|
@@ -43,6 +58,12 @@ def container_group(ctx):
|
|
|
43
58
|
default="community",
|
|
44
59
|
help="IRIS edition: community (default), enterprise (requires license), light (minimal for CI/CD)",
|
|
45
60
|
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--image",
|
|
63
|
+
type=str,
|
|
64
|
+
default=None,
|
|
65
|
+
help="Custom Docker image (overrides --edition). Example: myregistry/iris:2024.1",
|
|
66
|
+
)
|
|
46
67
|
@click.option(
|
|
47
68
|
"--license",
|
|
48
69
|
"license_key",
|
|
@@ -60,37 +81,31 @@ def container_group(ctx):
|
|
|
60
81
|
)
|
|
61
82
|
@click.option("--cpf", help="Path to CPF merge file or raw CPF content")
|
|
62
83
|
@click.pass_context
|
|
63
|
-
def up(ctx, config, name, edition, license_key, detach, timeout, cpf):
|
|
84
|
+
def up(ctx, config, name, edition, image, license_key, detach, timeout, cpf):
|
|
64
85
|
"""
|
|
65
86
|
Create and start IRIS container from configuration.
|
|
66
87
|
|
|
67
88
|
Similar to docker-compose up. Creates a new container or starts existing one.
|
|
68
89
|
Supports zero-config mode - works without any configuration file.
|
|
69
90
|
|
|
70
|
-
|
|
91
|
+
\b
|
|
92
|
+
Container Lifecycle:
|
|
71
93
|
- Containers persist until explicitly removed with 'container remove'
|
|
72
94
|
- Volume mounts are verified during creation
|
|
73
95
|
- No automatic cleanup when CLI exits
|
|
74
96
|
|
|
75
97
|
\b
|
|
76
98
|
Examples:
|
|
77
|
-
# Zero-config (uses Community edition defaults)
|
|
78
99
|
iris-devtester container up
|
|
79
|
-
|
|
80
|
-
# Light edition for CI/CD (85% smaller, faster startup)
|
|
81
100
|
iris-devtester container up --edition light
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
iris-devtester container up --edition enterprise --license /path/to/iris.key
|
|
85
|
-
|
|
86
|
-
# With custom container name
|
|
101
|
+
iris-devtester container up --edition enterprise --license ./iris.key
|
|
102
|
+
iris-devtester container up --image myregistry/iris:2024.1
|
|
87
103
|
iris-devtester container up --name my-test-db
|
|
88
|
-
|
|
89
|
-
# With custom configuration including volumes
|
|
90
104
|
iris-devtester container up --config iris-config.yml
|
|
91
105
|
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
\b
|
|
107
|
+
NOTE: For multi-container setups (sharding, mirroring, clusters),
|
|
108
|
+
use docker-compose instead. This tool manages single containers.
|
|
94
109
|
"""
|
|
95
110
|
try:
|
|
96
111
|
# Load configuration
|
|
@@ -112,8 +127,12 @@ def up(ctx, config, name, edition, license_key, detach, timeout, cpf):
|
|
|
112
127
|
container_config.container_name = name
|
|
113
128
|
click.echo(f" → Container name: {name}")
|
|
114
129
|
|
|
115
|
-
# Override
|
|
116
|
-
if
|
|
130
|
+
# Override image if provided via --image (takes precedence over --edition)
|
|
131
|
+
if image:
|
|
132
|
+
container_config.image = image
|
|
133
|
+
click.echo(f" → Image: {image} (custom)")
|
|
134
|
+
# Override edition if provided via --edition (only if --image not set)
|
|
135
|
+
elif edition:
|
|
117
136
|
edition_lower = edition.lower()
|
|
118
137
|
container_config.edition = edition_lower
|
|
119
138
|
|
|
@@ -5,6 +5,7 @@ from typing import Any, Optional
|
|
|
5
5
|
|
|
6
6
|
from iris_devtester.config import IRISConfig
|
|
7
7
|
from iris_devtester.connections import get_connection
|
|
8
|
+
from iris_devtester.containers.models import HealthCheckLevel, ValidationResult
|
|
8
9
|
|
|
9
10
|
logger = logging.getLogger(__name__)
|
|
10
11
|
|
|
@@ -59,6 +60,9 @@ class IRISContainer(IRISBase):
|
|
|
59
60
|
Enhanced IRIS container with automatic connection and password management.
|
|
60
61
|
"""
|
|
61
62
|
|
|
63
|
+
# Custom kwargs that should NOT be passed to parent/Docker SDK
|
|
64
|
+
_CUSTOM_KWARGS = {"port_registry", "preferred_port", "project_path"}
|
|
65
|
+
|
|
62
66
|
def __init__(
|
|
63
67
|
self,
|
|
64
68
|
image: str = "intersystemsdc/iris-community:latest",
|
|
@@ -70,6 +74,12 @@ class IRISContainer(IRISBase):
|
|
|
70
74
|
if not HAS_TESTCONTAINERS:
|
|
71
75
|
logger.warning("testcontainers not installed. Functionality will be limited.")
|
|
72
76
|
|
|
77
|
+
# Extract custom kwargs before passing to parent
|
|
78
|
+
self._port_registry = kwargs.pop("port_registry", None)
|
|
79
|
+
self._preferred_port = kwargs.pop("preferred_port", None)
|
|
80
|
+
self._project_path = kwargs.pop("project_path", None)
|
|
81
|
+
self._port_assignment = None # Will be set in start() if port_registry is used
|
|
82
|
+
|
|
73
83
|
super().__init__(image=image, **kwargs)
|
|
74
84
|
self._username = username
|
|
75
85
|
self._password = password
|
|
@@ -198,8 +208,72 @@ class IRISContainer(IRISBase):
|
|
|
198
208
|
def with_name(self, name: str) -> "IRISContainer":
|
|
199
209
|
"""Set the container name."""
|
|
200
210
|
self._container_name = name
|
|
201
|
-
|
|
202
|
-
|
|
211
|
+
# Use parent's _name attribute directly - do NOT use with_kwargs(name=...)
|
|
212
|
+
# as that causes duplicate 'name' kwarg in Docker's run() call
|
|
213
|
+
# (parent passes both name=self._name and **self._kwargs to run())
|
|
214
|
+
self._name = name
|
|
215
|
+
return self
|
|
216
|
+
|
|
217
|
+
def with_cpf_merge(self, cpf_content_or_path: str) -> "IRISContainer":
|
|
218
|
+
"""Configure CPF merge for IRIS startup customization.
|
|
219
|
+
|
|
220
|
+
CPF merge allows customizing IRIS configuration at startup time
|
|
221
|
+
using a merge file that is applied during container initialization.
|
|
222
|
+
This enables features like:
|
|
223
|
+
- Enabling CallIn service automatically
|
|
224
|
+
- Setting memory configuration
|
|
225
|
+
- Pre-configuring users and security settings
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
cpf_content_or_path: Either a CPF merge content string or a
|
|
229
|
+
path to a CPF merge file. If the string contains newlines
|
|
230
|
+
or CPF section markers like "[Actions]", it's treated as
|
|
231
|
+
content. Otherwise, it's treated as a file path.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Self for method chaining.
|
|
235
|
+
|
|
236
|
+
Examples:
|
|
237
|
+
>>> # From preset content
|
|
238
|
+
>>> iris = IRISContainer.community().with_cpf_merge(CPFPreset.ENABLE_CALLIN)
|
|
239
|
+
|
|
240
|
+
>>> # From file path
|
|
241
|
+
>>> iris = IRISContainer.community().with_cpf_merge("/path/to/merge.cpf")
|
|
242
|
+
"""
|
|
243
|
+
import os
|
|
244
|
+
import tempfile
|
|
245
|
+
|
|
246
|
+
# Determine if it's content or a file path
|
|
247
|
+
is_content = "\n" in cpf_content_or_path or "[" in cpf_content_or_path
|
|
248
|
+
|
|
249
|
+
if is_content:
|
|
250
|
+
# Write content to a temporary file
|
|
251
|
+
# Note: The temp file needs to persist until container is started
|
|
252
|
+
if not hasattr(self, "_cpf_temp_files"):
|
|
253
|
+
self._cpf_temp_files = []
|
|
254
|
+
|
|
255
|
+
fd, temp_path = tempfile.mkstemp(suffix=".cpf", prefix="iris_merge_")
|
|
256
|
+
os.write(fd, cpf_content_or_path.encode("utf-8"))
|
|
257
|
+
os.close(fd)
|
|
258
|
+
self._cpf_temp_files.append(temp_path)
|
|
259
|
+
host_path = temp_path
|
|
260
|
+
else:
|
|
261
|
+
# Treat as file path
|
|
262
|
+
host_path = os.path.abspath(cpf_content_or_path)
|
|
263
|
+
if not os.path.exists(host_path):
|
|
264
|
+
raise FileNotFoundError(f"CPF merge file not found: {host_path}")
|
|
265
|
+
|
|
266
|
+
# Container path for the merge file
|
|
267
|
+
container_path = "/tmp/merge.cpf"
|
|
268
|
+
|
|
269
|
+
# Mount the CPF file into the container
|
|
270
|
+
if hasattr(self, "with_volume_mapping"):
|
|
271
|
+
self.with_volume_mapping(host_path, container_path, "ro")
|
|
272
|
+
|
|
273
|
+
# Set the environment variable to tell IRIS to use the merge file
|
|
274
|
+
if hasattr(self, "with_env"):
|
|
275
|
+
self.with_env("ISC_CPF_MERGE_FILE", container_path)
|
|
276
|
+
|
|
203
277
|
return self
|
|
204
278
|
|
|
205
279
|
def get_container_name(self) -> str:
|
|
@@ -304,14 +378,19 @@ class IRISContainer(IRISBase):
|
|
|
304
378
|
self.execute_objectscript(script, namespace="%SYS")
|
|
305
379
|
|
|
306
380
|
def get_config(self) -> IRISConfig:
|
|
307
|
-
"""Get connection configuration.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
381
|
+
"""Get connection configuration.
|
|
382
|
+
|
|
383
|
+
Note: Credentials are always read fresh from _username/_password
|
|
384
|
+
to support credential updates after container start.
|
|
385
|
+
"""
|
|
386
|
+
# Always create fresh config to pick up any credential changes
|
|
387
|
+
# (e.g., conftest may update _username/_password after start())
|
|
388
|
+
self._config = IRISConfig(
|
|
389
|
+
username=self._username,
|
|
390
|
+
password=self._password,
|
|
391
|
+
namespace=self._namespace,
|
|
392
|
+
container_name=self.get_container_name(),
|
|
393
|
+
)
|
|
315
394
|
config = self._config
|
|
316
395
|
try:
|
|
317
396
|
# Get host and mapped port from testcontainers
|
|
@@ -379,20 +458,91 @@ class IRISContainer(IRISBase):
|
|
|
379
458
|
return self
|
|
380
459
|
|
|
381
460
|
def start(self) -> "IRISContainer":
|
|
382
|
-
"""Start container with pre-config support."""
|
|
461
|
+
"""Start container with pre-config support and port registry integration."""
|
|
383
462
|
if self._preconfigure_password:
|
|
384
463
|
self.with_env("IRIS_PASSWORD", self._preconfigure_password)
|
|
385
464
|
if self._preconfigure_username:
|
|
386
465
|
self.with_env("IRIS_USERNAME", self._preconfigure_username)
|
|
387
466
|
|
|
467
|
+
# Port registry integration: assign port before starting
|
|
468
|
+
if self._port_registry is not None and self._project_path is not None:
|
|
469
|
+
from iris_devtester.ports import PortAssignment
|
|
470
|
+
|
|
471
|
+
self._port_assignment = self._port_registry.assign_port(
|
|
472
|
+
project_path=self._project_path,
|
|
473
|
+
preferred_port=self._preferred_port,
|
|
474
|
+
)
|
|
475
|
+
# Configure container to use the assigned port
|
|
476
|
+
# Note: testcontainers uses with_bind_ports() for port mapping
|
|
477
|
+
if hasattr(self, "with_bind_ports"):
|
|
478
|
+
self.with_bind_ports(1972, self._port_assignment.port)
|
|
479
|
+
|
|
388
480
|
super().start()
|
|
389
481
|
# Ensure host/port are updated after start
|
|
390
482
|
self.get_config()
|
|
391
483
|
self._password_preconfigured = True
|
|
392
484
|
return self
|
|
393
485
|
|
|
486
|
+
def get_assigned_port(self) -> Optional[int]:
|
|
487
|
+
"""
|
|
488
|
+
Get the port assigned by the port registry.
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
The assigned port number, or None if no port registry was used.
|
|
492
|
+
"""
|
|
493
|
+
if hasattr(self, "_port_assignment") and self._port_assignment is not None:
|
|
494
|
+
return self._port_assignment.port
|
|
495
|
+
return None
|
|
496
|
+
|
|
497
|
+
def get_project_path(self) -> Optional[str]:
|
|
498
|
+
"""
|
|
499
|
+
Get the project path associated with this container.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
The project path, or None if no port registry was used.
|
|
503
|
+
"""
|
|
504
|
+
return self._project_path
|
|
505
|
+
|
|
394
506
|
def wait_for_ready(self, timeout: int = 60) -> bool:
|
|
395
507
|
"""Wait for IRIS to be ready."""
|
|
396
508
|
# Simple wait for prototype
|
|
397
509
|
time.sleep(15)
|
|
398
510
|
return True
|
|
511
|
+
|
|
512
|
+
def validate(self, level: HealthCheckLevel = HealthCheckLevel.STANDARD) -> ValidationResult:
|
|
513
|
+
"""Validate this container's health status.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
level: Validation depth level (MINIMAL, STANDARD, or FULL).
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
ValidationResult with success status, details, and remediation steps.
|
|
520
|
+
|
|
521
|
+
Examples:
|
|
522
|
+
>>> with IRISContainer.community() as iris:
|
|
523
|
+
... result = iris.validate()
|
|
524
|
+
... assert result.success is True
|
|
525
|
+
"""
|
|
526
|
+
# Import here to avoid circular import
|
|
527
|
+
from iris_devtester.containers.validation import validate_container
|
|
528
|
+
|
|
529
|
+
container_name = self.get_container_name()
|
|
530
|
+
return validate_container(container_name=container_name, level=level)
|
|
531
|
+
|
|
532
|
+
def assert_healthy(self, level: HealthCheckLevel = HealthCheckLevel.STANDARD) -> None:
|
|
533
|
+
"""Assert that this container is healthy, raising RuntimeError if not.
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
level: Validation depth level (MINIMAL, STANDARD, or FULL).
|
|
537
|
+
|
|
538
|
+
Raises:
|
|
539
|
+
RuntimeError: If container validation fails, with structured error
|
|
540
|
+
message including "What went wrong" and "How to fix it" sections.
|
|
541
|
+
|
|
542
|
+
Examples:
|
|
543
|
+
>>> with IRISContainer.community() as iris:
|
|
544
|
+
... iris.assert_healthy() # No exception = healthy
|
|
545
|
+
"""
|
|
546
|
+
result = self.validate(level=level)
|
|
547
|
+
if not result.success:
|
|
548
|
+
raise RuntimeError(result.format_message())
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
"""IRIS
|
|
1
|
+
"""IRIS Fixture Management.
|
|
2
2
|
|
|
3
3
|
This module provides tools for creating, loading, and validating IRIS database
|
|
4
|
-
fixtures
|
|
5
|
-
|
|
4
|
+
fixtures. Fixtures enable fast, reproducible test data setup by exporting
|
|
5
|
+
database namespaces to version-controlled files.
|
|
6
6
|
|
|
7
7
|
Key Features:
|
|
8
|
-
- Create fixtures from IRIS namespaces (
|
|
9
|
-
- Load fixtures via
|
|
8
|
+
- Create fixtures from IRIS namespaces (globals + class definitions)
|
|
9
|
+
- Load fixtures via global import (<1 second for most fixtures)
|
|
10
10
|
- Validate fixture integrity with SHA256 checksums
|
|
11
11
|
- CLI commands for fixture management
|
|
12
12
|
|
|
13
|
+
Fixture Format:
|
|
14
|
+
Fixtures are stored as directories containing:
|
|
15
|
+
- manifest.json: Metadata, checksums, and table information
|
|
16
|
+
- globals.gof: Global data in IRIS %GOF format
|
|
17
|
+
- classes.xml: Class definitions (optional, for SQL tables)
|
|
18
|
+
|
|
13
19
|
Example:
|
|
14
|
-
>>> from iris_devtester.fixtures import
|
|
20
|
+
>>> from iris_devtester.fixtures import FixtureLoader, FixtureCreator
|
|
15
21
|
>>>
|
|
16
22
|
>>> # Create fixture from existing namespace
|
|
17
23
|
>>> creator = FixtureCreator(container=iris_container)
|
|
@@ -22,7 +28,7 @@ Example:
|
|
|
22
28
|
... )
|
|
23
29
|
>>>
|
|
24
30
|
>>> # Load fixture into new namespace
|
|
25
|
-
>>> loader =
|
|
31
|
+
>>> loader = FixtureLoader(container=iris_container)
|
|
26
32
|
>>> target_ns = iris_container.get_test_namespace(prefix="LOADED")
|
|
27
33
|
>>> result = loader.load_fixture(
|
|
28
34
|
... fixture_path="./fixtures/test-data",
|
|
@@ -35,7 +41,7 @@ pytest Integration:
|
|
|
35
41
|
|
|
36
42
|
@pytest.fixture
|
|
37
43
|
def loaded_fixture(iris_container):
|
|
38
|
-
loader =
|
|
44
|
+
loader = FixtureLoader(container=iris_container)
|
|
39
45
|
target_ns = iris_container.get_test_namespace(prefix="TEST")
|
|
40
46
|
result = loader.load_fixture(
|
|
41
47
|
fixture_path="./fixtures/test-data",
|
|
@@ -53,7 +59,7 @@ pytest Integration:
|
|
|
53
59
|
__version__ = "0.1.0"
|
|
54
60
|
|
|
55
61
|
from .creator import FixtureCreator
|
|
56
|
-
from .loader import
|
|
62
|
+
from .loader import FixtureLoader
|
|
57
63
|
|
|
58
64
|
# Import data models and exceptions
|
|
59
65
|
from .manifest import (
|
|
@@ -83,6 +89,9 @@ from .obj_export import (
|
|
|
83
89
|
# Import validator, loader, and creator
|
|
84
90
|
from .validator import FixtureValidator
|
|
85
91
|
|
|
92
|
+
# Backward compatibility alias
|
|
93
|
+
DATFixtureLoader = FixtureLoader
|
|
94
|
+
|
|
86
95
|
# Public API
|
|
87
96
|
__all__ = [
|
|
88
97
|
# Data models
|
|
@@ -98,8 +107,10 @@ __all__ = [
|
|
|
98
107
|
"ChecksumMismatchError",
|
|
99
108
|
# Classes
|
|
100
109
|
"FixtureValidator",
|
|
101
|
-
"
|
|
110
|
+
"FixtureLoader",
|
|
102
111
|
"FixtureCreator",
|
|
112
|
+
# Backward compatibility
|
|
113
|
+
"DATFixtureLoader",
|
|
103
114
|
# $SYSTEM.OBJ utilities (Feature 017)
|
|
104
115
|
"ExportResult",
|
|
105
116
|
"ImportResult",
|
|
@@ -17,7 +17,12 @@ from .validator import FixtureValidator
|
|
|
17
17
|
|
|
18
18
|
class FixtureCreator:
|
|
19
19
|
"""
|
|
20
|
-
Creates
|
|
20
|
+
Creates IRIS fixtures by exporting namespace globals and class definitions.
|
|
21
|
+
|
|
22
|
+
Fixture Format:
|
|
23
|
+
- globals.gof: Global data in IRIS %GOF format
|
|
24
|
+
- classes.xml: Class definitions for SQL tables
|
|
25
|
+
- manifest.json: Metadata and checksums
|
|
21
26
|
"""
|
|
22
27
|
|
|
23
28
|
def __init__(
|
|
@@ -64,7 +69,12 @@ class FixtureCreator:
|
|
|
64
69
|
from iris_devtester.config import discover_config
|
|
65
70
|
from iris_devtester.connections import get_connection as get_conn_factory
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
# Use container's config if available, otherwise fall back to discovery
|
|
73
|
+
base_config = self.connection_config
|
|
74
|
+
if base_config is None and self.container is not None:
|
|
75
|
+
base_config = self.container.get_config()
|
|
76
|
+
if base_config is None:
|
|
77
|
+
base_config = discover_config()
|
|
68
78
|
ns_config = dataclasses.replace(base_config, namespace=namespace)
|
|
69
79
|
|
|
70
80
|
ns_connection = get_conn_factory(ns_config)
|
|
@@ -223,7 +233,12 @@ class FixtureCreator:
|
|
|
223
233
|
get_connection as get_modern_connection,
|
|
224
234
|
)
|
|
225
235
|
|
|
226
|
-
|
|
236
|
+
# If container is provided but no explicit connection_config, use container's config
|
|
237
|
+
config = self.connection_config
|
|
238
|
+
if config is None and self.container is not None:
|
|
239
|
+
config = self.container.get_config()
|
|
240
|
+
|
|
241
|
+
self._connection = get_modern_connection(config)
|
|
227
242
|
return self._connection
|
|
228
243
|
|
|
229
244
|
def _get_iris_version(self) -> str:
|
|
@@ -12,7 +12,7 @@ from .manifest import FixtureLoadError, FixtureManifest, FixtureValidationError,
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class
|
|
15
|
+
class FixtureLoader:
|
|
16
16
|
|
|
17
17
|
def __init__(self, container: Optional[IRISContainer] = None, **kwargs):
|
|
18
18
|
self.container = container
|
|
@@ -45,14 +45,17 @@ class DATFixtureLoader:
|
|
|
45
45
|
) -> LoadResult:
|
|
46
46
|
start_time = time.time()
|
|
47
47
|
|
|
48
|
+
# Validate fixture exists BEFORE starting a container (Fail Fast)
|
|
49
|
+
manifest = self._load_manifest(fixture_path)
|
|
50
|
+
|
|
48
51
|
if not self.container:
|
|
49
52
|
self.container = IRISContainer.community()
|
|
50
53
|
self.container.start()
|
|
51
54
|
self._owns_container = True
|
|
52
55
|
|
|
53
56
|
try:
|
|
54
|
-
manifest = self._load_manifest(fixture_path)
|
|
55
57
|
namespace = target_namespace or manifest.namespace
|
|
58
|
+
# Note: manifest already loaded above (fail-fast before container start)
|
|
56
59
|
|
|
57
60
|
dat_file_path = Path(fixture_path) / "IRIS.DAT"
|
|
58
61
|
if not dat_file_path.exists():
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
"""Fixture manifest data models and validation.
|
|
2
2
|
|
|
3
|
-
This module defines the data structures for IRIS
|
|
3
|
+
This module defines the data structures for IRIS fixture manifests,
|
|
4
4
|
including FixtureManifest, TableInfo, ValidationResult, and LoadResult.
|
|
5
|
+
|
|
6
|
+
Fixture Format:
|
|
7
|
+
Fixtures are stored as directories containing:
|
|
8
|
+
- manifest.json: Metadata, checksums, and table information
|
|
9
|
+
- globals.gof: Global data in IRIS %GOF format
|
|
10
|
+
- classes.xml: Class definitions (optional, for SQL tables)
|
|
5
11
|
"""
|
|
6
12
|
|
|
7
13
|
import json
|
|
@@ -46,7 +52,6 @@ class TableInfo:
|
|
|
46
52
|
"""
|
|
47
53
|
Information about a single table in a fixture.
|
|
48
54
|
|
|
49
|
-
Note: All tables are stored in a single IRIS.DAT file.
|
|
50
55
|
This class tracks which tables are included in the fixture.
|
|
51
56
|
|
|
52
57
|
Attributes:
|
|
@@ -69,11 +74,12 @@ class TableInfo:
|
|
|
69
74
|
@dataclass
|
|
70
75
|
class FixtureManifest:
|
|
71
76
|
"""
|
|
72
|
-
Manifest describing
|
|
77
|
+
Manifest describing an IRIS fixture.
|
|
73
78
|
|
|
74
79
|
A fixture is a directory containing:
|
|
75
80
|
- manifest.json (this schema)
|
|
76
|
-
-
|
|
81
|
+
- globals.gof (global data in %GOF format)
|
|
82
|
+
- classes.xml (optional, class definitions for SQL tables)
|
|
77
83
|
|
|
78
84
|
Example manifest.json:
|
|
79
85
|
{
|
|
@@ -93,6 +99,9 @@ class FixtureManifest:
|
|
|
93
99
|
}
|
|
94
100
|
]
|
|
95
101
|
}
|
|
102
|
+
|
|
103
|
+
Note: The dat_file field is kept for backward compatibility but the actual
|
|
104
|
+
data is stored in globals.gof (GOF format).
|
|
96
105
|
"""
|
|
97
106
|
|
|
98
107
|
# Required fields
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
"""IRIS
|
|
1
|
+
"""IRIS Fixture Validator.
|
|
2
2
|
|
|
3
3
|
This module provides the FixtureValidator class for validating fixture
|
|
4
4
|
integrity including manifest structure, file existence, and SHA256 checksums.
|
|
5
|
+
|
|
6
|
+
Fixture Format:
|
|
7
|
+
Fixtures are stored as directories containing:
|
|
8
|
+
- manifest.json: Metadata, checksums, and table information
|
|
9
|
+
- globals.gof: Global data in IRIS %GOF format
|
|
10
|
+
- classes.xml: Class definitions (optional, for SQL tables)
|
|
5
11
|
"""
|
|
6
12
|
|
|
7
13
|
import hashlib
|
|
@@ -18,11 +24,11 @@ from .manifest import (
|
|
|
18
24
|
|
|
19
25
|
class FixtureValidator:
|
|
20
26
|
"""
|
|
21
|
-
Validates
|
|
27
|
+
Validates fixture integrity.
|
|
22
28
|
|
|
23
29
|
This is a stateless validator that checks:
|
|
24
30
|
- Manifest structure and required fields
|
|
25
|
-
- File existence (manifest.json,
|
|
31
|
+
- File existence (manifest.json, globals.gof)
|
|
26
32
|
- SHA256 checksum matching
|
|
27
33
|
- Fixture size statistics
|
|
28
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iris-devtester
|
|
3
|
-
Version: 1.10.
|
|
3
|
+
Version: 1.10.2
|
|
4
4
|
Summary: Battle-tested InterSystems IRIS infrastructure utilities for Python development
|
|
5
5
|
Author-email: InterSystems Community <community@intersystems.com>
|
|
6
6
|
Maintainer-email: Thomas Dyar <thomas.dyar@intersystems.com>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
iris_devtester/__init__.py,sha256=
|
|
2
|
-
iris_devtester/cli/__init__.py,sha256=
|
|
1
|
+
iris_devtester/__init__.py,sha256=7Ud7QRVdnZQ205YQ02_a-OFoKidWXBeTS9peUW6u60g,1849
|
|
2
|
+
iris_devtester/cli/__init__.py,sha256=Gs11Sj_v4OaKWBf2-dCaUB7RwXSxwGcuYjn8rHgsnRU,1914
|
|
3
3
|
iris_devtester/cli/__main__.py,sha256=YgFAn0Q02AXgXRwAg70MR3U0IIjFHUSz38h5tPFJnGg,125
|
|
4
4
|
iris_devtester/cli/connection_commands.py,sha256=cabAufSsrrhwpxlPZFlsE-5Zi54176eGdJOApPLdZXA,11467
|
|
5
|
-
iris_devtester/cli/container.py,sha256=
|
|
5
|
+
iris_devtester/cli/container.py,sha256=ZVWmoxCzZPezjSNPQZRJLdGmeJCJzs7yw_iqK-UVt-Y,37763
|
|
6
6
|
iris_devtester/cli/container_commands.py,sha256=lOxa2TXnBK1ATCIzbbXexk-p0lItStUwU_gKVnv_p1w,4451
|
|
7
7
|
iris_devtester/cli/fixture_commands.py,sha256=w1n6MIujQsY46ZKG_L0aRF4lhohWh5dOrF0lgH_K3r0,17493
|
|
8
8
|
iris_devtester/config/__init__.py,sha256=acRVXpdKAp7lkZzrpKGnOYLgSfkxN3kpL1RU0b0hhlM,486
|
|
@@ -24,19 +24,19 @@ iris_devtester/connections/models.py,sha256=rfKCFtm86wQZAv_cLOVS6N7P5vGlfkInSHbs
|
|
|
24
24
|
iris_devtester/connections/retry.py,sha256=FxNhlTA2RHI2ayvEF5w7P_EjO7k6xKgeA3mplJd3-co,2893
|
|
25
25
|
iris_devtester/containers/__init__.py,sha256=quWN4CtxRUMje8QkpZWMY79XlVOSM79HF5ewHLNz5YA,729
|
|
26
26
|
iris_devtester/containers/cpf_manager.py,sha256=GBkuM5mPw85lo7djINlTNkrQ8bGgRe2QEWv7YQQL6KU,1969
|
|
27
|
-
iris_devtester/containers/iris_container.py,sha256=
|
|
27
|
+
iris_devtester/containers/iris_container.py,sha256=VdxX88785sY6WEj-NtV-BjTrhVpd2lXvMzzMX8xJORw,20276
|
|
28
28
|
iris_devtester/containers/models.py,sha256=f66fwQjlmw1Sl4M33QlglCv4ewNtk4TJLXXcmhNsbDA,13693
|
|
29
29
|
iris_devtester/containers/monitor_utils.py,sha256=cCsLp1cZYAbMmj7o6tUYhVurMEE7GOOe9Y5FLAxl6hM,4917
|
|
30
30
|
iris_devtester/containers/monitoring.py,sha256=E4v-5VW-_W7CVSRm_ryzpOfLQgP9NZ6WN7GYglsAFro,41172
|
|
31
31
|
iris_devtester/containers/performance.py,sha256=fNLUqsTDgPrIBb2sP2ubVPM5oVconrXA1cn6Bq_2JvM,11272
|
|
32
32
|
iris_devtester/containers/validation.py,sha256=SUOsEc2UPHJTYosSweSmF8pcDLYnrvXZNOaApTu7PQc,14564
|
|
33
33
|
iris_devtester/containers/wait_strategies.py,sha256=cUJNU8eD8EWKSaQvEWqkCKkBPCotnT_NBrw2J70ZXVA,7754
|
|
34
|
-
iris_devtester/fixtures/__init__.py,sha256=
|
|
35
|
-
iris_devtester/fixtures/creator.py,sha256=
|
|
36
|
-
iris_devtester/fixtures/loader.py,sha256=
|
|
37
|
-
iris_devtester/fixtures/manifest.py,sha256=
|
|
34
|
+
iris_devtester/fixtures/__init__.py,sha256=foqK_3CCjvIvEipiD_PmXKLclHXq2qPBbdRpewnxiLQ,3491
|
|
35
|
+
iris_devtester/fixtures/creator.py,sha256=L3mQ_AFw2NumDOS9zoQnuv3t3z6MMAR9_5-nrNi99Kg,8716
|
|
36
|
+
iris_devtester/fixtures/loader.py,sha256=oUQdlCX17Abr4muu8dj_sLVxumQ5-hDwxy7So6A0sw8,12646
|
|
37
|
+
iris_devtester/fixtures/manifest.py,sha256=FZUjdzOZfkBJSncglPhD_Xc29cV1z7tjkQExA7Af8lY,10324
|
|
38
38
|
iris_devtester/fixtures/obj_export.py,sha256=avJtCtLgs4DW5gFcLb6dzPEH_g5lhbYMnCVvpFW4T2Y,14753
|
|
39
|
-
iris_devtester/fixtures/validator.py,sha256=
|
|
39
|
+
iris_devtester/fixtures/validator.py,sha256=gnYAYVNfH2xaD4gFgbtfeZ4pQuV5x0MpQ4djt_VLOIQ,11911
|
|
40
40
|
iris_devtester/integrations/__init__.py,sha256=wMduwyadsOOwZ8odfVNZkjeKg-u2APnvMz_jdvWaL0A,368
|
|
41
41
|
iris_devtester/integrations/langchain.py,sha256=cIV8bq_wDKMOXLFQ1P-wdSFsJdxIV7o0-a5f6-ELbME,11310
|
|
42
42
|
iris_devtester/ports/__init__.py,sha256=uLdbV7J7UJxJsoDqoyySTabIle72WltYDFKlN2-f7bI,506
|
|
@@ -58,9 +58,9 @@ iris_devtester/utils/iris_container_adapter.py,sha256=0Vc55gDsVAxHZzk4wvZySGUJKR
|
|
|
58
58
|
iris_devtester/utils/password.py,sha256=OqS6LNXMDopSoYGiodM2V9clghD4qcbIzftH4TdrMSU,21968
|
|
59
59
|
iris_devtester/utils/progress.py,sha256=5OmuntItqCgUFXSmaQjHwILrTnVLYECZ5glpSbvOTOU,7795
|
|
60
60
|
iris_devtester/utils/test_connection.py,sha256=cyEzxSbvxBxz44xLb7Jbz8OhHT8UgCZLO0lT-qShPEo,6950
|
|
61
|
-
iris_devtester-1.10.
|
|
62
|
-
iris_devtester-1.10.
|
|
63
|
-
iris_devtester-1.10.
|
|
64
|
-
iris_devtester-1.10.
|
|
65
|
-
iris_devtester-1.10.
|
|
66
|
-
iris_devtester-1.10.
|
|
61
|
+
iris_devtester-1.10.2.dist-info/licenses/LICENSE,sha256=dISbikDYS2uP710ZFzSzaSmKzIBRyi_6YwuwO97bT94,1083
|
|
62
|
+
iris_devtester-1.10.2.dist-info/METADATA,sha256=sh5xjenarcua9puUzAl1Lpf3hEGpbHpp5npOUXeYPfE,8940
|
|
63
|
+
iris_devtester-1.10.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
64
|
+
iris_devtester-1.10.2.dist-info/entry_points.txt,sha256=svOltni3vX3ul_qxbWl8fOA6ZrJeFASuN29pON6ftqw,59
|
|
65
|
+
iris_devtester-1.10.2.dist-info/top_level.txt,sha256=DnrLJ3laB5x_gTkmATDEg1v1lTOQxgmHgNd66bdXaoU,15
|
|
66
|
+
iris_devtester-1.10.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|