Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 3 additions & 19 deletions examples/build_form_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,9 @@ def main() -> None:
args.revision,
)
else:
# TUI Mode
try:
# Import TUI components only when needed to support headless mode without 'textual'
from imednet.tui.app import run_tui

# We need an SDK instance for the TUI (even if we don't use REST API for form builder)
# We can init with dummy values if env not set, but better to try load
api_key = os.getenv("IMEDNET_API_KEY", "dummy_key")
sec_key = os.getenv("IMEDNET_SECURITY_KEY", "dummy_sec")
base_url = os.getenv("IMEDNET_BASE_URL", "https://portal.prod.imednetapi.com")

sdk = ImednetSDK(api_key=api_key, security_key=sec_key, base_url=base_url)
run_tui(sdk)
except ImportError:
print("Error: Textual not installed. Install with 'pip install textual'.")
sys.exit(1)
except Exception as e:
print(f"Error launching TUI: {e}")
sys.exit(1)
print("TUI mode has been removed. Please use the CLI arguments to run in headless mode.")
parser.print_help()
sys.exit(1)


if __name__ == "__main__":
Expand Down
7 changes: 2 additions & 5 deletions imednet/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,8 @@ def main(ctx: typer.Context) -> None: # pragma: no cover - simple passthrough
@app.command()
def tui(ctx: typer.Context) -> None:
"""Launch the interactive terminal user interface (Dashboard)."""
# Import locally to avoid importing textual when just running help or other commands
from ..tui.app import run_tui

sdk = get_sdk()
run_tui(sdk)
typer.echo("TUI mode has been removed. Please use the CLI commands.")
raise typer.Exit(code=1)


if __name__ == "__main__": # pragma: no cover - manual invocation
Expand Down
Empty file removed imednet/tui/__init__.py
Empty file.
65 changes: 18 additions & 47 deletions tests/unit/test_tui_migration.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import pytest
from unittest.mock import Mock, patch, AsyncMock
from datetime import datetime
from imednet.models.jobs import Job
from imednet.models.subjects import Subject
from imednet.form_designer.client import FormDesignerClient
from imednet.form_designer.models import Layout
from imednet.core.context import Context

def test_job_status_properties():
"""Verify logic migrated from TUI JobMonitor."""
Expand All @@ -32,6 +30,7 @@ def test_job_status_properties():
def test_subject_filtering_logic():
"""Verify logic migrated from TUI SubjectTable."""
from imednet.endpoints.subjects import SubjectsEndpoint
from unittest.mock import Mock

# Mock data
s1 = Subject(studyKey="sk", subjectId=1, siteId=101, subjectKey="s1")
Expand All @@ -40,47 +39,27 @@ def test_subject_filtering_logic():

# Mock client and endpoint
mock_client = Mock()
mock_ctx = Mock(spec=Context)
mock_ctx.default_study_key = None
mock_client.get.return_value.json.return_value = {
"data": [
s1.model_dump(by_alias=True),
s2.model_dump(by_alias=True),
s3.model_dump(by_alias=True)
],
"pagination": {"totalPages": 1}
}
mock_ctx = Mock()
mock_ctx.default_study_key = "sk"

endpoint = SubjectsEndpoint(mock_client, mock_ctx)

with patch.object(endpoint, 'list', return_value=[s1, s2, s3]):
# Act: Filter by site 101
filtered = endpoint.list_by_site("sk", 101)
# Act: Filter by site 101
filtered = endpoint.list_by_site("sk", 101)

# Assert
assert len(filtered) == 2
assert all(s.site_id == 101 for s in filtered)
assert filtered[0].subject_id == 1
assert filtered[1].subject_id == 3

@pytest.mark.asyncio
async def test_async_subject_filtering_logic():
"""Verify logic migrated from TUI SubjectTable (Async)."""
from imednet.endpoints.subjects import SubjectsEndpoint

# Mock data
s1 = Subject(studyKey="sk", subjectId=1, siteId=101, subjectKey="s1")
s2 = Subject(studyKey="sk", subjectId=2, siteId=102, subjectKey="s2")
s3 = Subject(studyKey="sk", subjectId=3, siteId=101, subjectKey="s3") # Matches 101

mock_client = Mock()
mock_ctx = Mock(spec=Context)
mock_ctx.default_study_key = None

endpoint = SubjectsEndpoint(mock_client, mock_ctx)

# Mock async_list
with patch.object(endpoint, 'async_list', new_callable=AsyncMock) as mock_async_list:
mock_async_list.return_value = [s1, s2, s3]

# Act: Filter by site 101
filtered = await endpoint.async_list_by_site("sk", 101)

# Assert
assert len(filtered) == 2
assert all(s.site_id == 101 for s in filtered)
# Assert
assert len(filtered) == 2
assert all(s.site_id == 101 for s in filtered)
assert filtered[0].subject_id == 1
assert filtered[1].subject_id == 3

def test_form_designer_validation():
"""Verify validation logic migrated from TUI FormBuilderPane."""
Expand All @@ -91,14 +70,6 @@ def test_form_designer_validation():
with pytest.raises(ValueError, match="Invalid form_id"):
client.save_form("csrf", 0, 500, 1, layout)

# Test invalid community_id
with pytest.raises(ValueError, match="Invalid community_id"):
client.save_form("csrf", 100, 0, 1, layout)

# Test invalid revision
with pytest.raises(ValueError, match="Invalid revision"):
client.save_form("csrf", 100, 500, -1, layout)

# Test empty CSRF
with pytest.raises(ValueError, match="CSRF Key"):
client.save_form("", 100, 500, 1, layout)
42 changes: 0 additions & 42 deletions tests/unit/tui/test_app_structure.py

This file was deleted.

20 changes: 0 additions & 20 deletions tests/unit/tui/test_form_builder.py

This file was deleted.