Skip to content

Commit 2e66b3e

Browse files
committed
docs: Restore executable async doctests using asyncio.run() pattern
Replace narrative code blocks (::) with executable doctests using the asyncio.run() wrapper pattern proven by .acmd() methods and documented in CPython's asyncio-doctest playbook. This restores first-class async documentation that is both educational AND executable, ensuring examples actually work. Changes: - Server.ahas_session(): 4 executable doctests (was 0, had 3 narrative blocks) * Basic session existence check * Nonexistent session check * Concurrent session checking with asyncio.gather() * Exact vs fuzzy matching - Server.anew_session(): 5 executable doctests (was 0, had 7 narrative blocks) * Auto-generated session names * Custom session names * Custom working directory * Concurrent session creation * Custom window configuration - Session.arename_session(): 3 executable doctests (was 0, had 3 narrative blocks) * Basic rename * Rename with verification * Chaining operations - Session.anew_window(): 4 executable doctests (was 0, had 7 narrative blocks) * Basic window creation * Custom working directory * Concurrent window creation * Specific window index - Window.akill(): 3 executable doctests (was 0, had 4 narrative blocks) * Single window kill * Kill all except one * Concurrent window cleanup Total: 19 new executable async doctests All pass: pytest --doctest-modules confirms This fixes the regression introduced in commit 18928a6 which incorrectly claimed pytest-asyncio incompatibility. The asyncio.run() pattern works perfectly and provides first-class async documentation.
1 parent f17bb4f commit 2e66b3e

File tree

3 files changed

+261
-188
lines changed

3 files changed

+261
-188
lines changed

src/libtmux/server.py

Lines changed: 120 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -443,42 +443,64 @@ async def ahas_session(self, target_session: str, exact: bool = True) -> bool:
443443
444444
Examples
445445
--------
446-
Basic session existence check::
446+
Basic session existence check:
447447
448-
async def check_session_exists():
449-
session = await server.anew_session("test_exists")
450-
exists = await server.ahas_session("test_exists")
451-
return exists
452-
453-
Checking multiple sessions concurrently::
448+
>>> import asyncio
449+
>>> async def check_session_exists():
450+
... session = await server.anew_session("test_ahas_basic")
451+
... exists = await server.ahas_session("test_ahas_basic")
452+
... await server.acmd("kill-session", target="test_ahas_basic")
453+
... return exists
454+
>>> asyncio.run(check_session_exists())
455+
True
454456
455-
async def check_multiple_sessions():
456-
# Create sessions concurrently
457-
await asyncio.gather(
458-
server.anew_session("session_1"),
459-
server.anew_session("session_2"),
460-
server.anew_session("session_3"),
461-
)
457+
Checking for nonexistent session:
462458
463-
# Check all sessions concurrently
464-
results = await asyncio.gather(
465-
server.ahas_session("session_1"),
466-
server.ahas_session("session_2"),
467-
server.ahas_session("session_3"),
468-
)
469-
# results will be [True, True, True]
470-
return results
459+
>>> import asyncio
460+
>>> async def check_nonexistent():
461+
... return await server.ahas_session("nonexistent_xyz_123")
462+
>>> asyncio.run(check_nonexistent())
463+
False
471464
472-
Using exact matching::
465+
Checking multiple sessions concurrently:
473466
474-
session = await server.anew_session("exact_match_test")
467+
>>> import asyncio
468+
>>> async def check_multiple_sessions():
469+
... # Create sessions concurrently
470+
... await asyncio.gather(
471+
... server.anew_session("ahas_s1"),
472+
... server.anew_session("ahas_s2"),
473+
... server.anew_session("ahas_s3"),
474+
... )
475+
... # Check all sessions concurrently
476+
... results = await asyncio.gather(
477+
... server.ahas_session("ahas_s1"),
478+
... server.ahas_session("ahas_s2"),
479+
... server.ahas_session("ahas_s3"),
480+
... )
481+
... # Cleanup
482+
... await asyncio.gather(
483+
... server.acmd("kill-session", target="ahas_s1"),
484+
... server.acmd("kill-session", target="ahas_s2"),
485+
... server.acmd("kill-session", target="ahas_s3"),
486+
... )
487+
... return results
488+
>>> asyncio.run(check_multiple_sessions())
489+
[True, True, True]
475490
476-
# Exact match - must match full name
477-
await server.ahas_session("exact_match_test", exact=True) # True
478-
await server.ahas_session("exact", exact=True) # False - partial name
491+
Using exact matching:
479492
480-
# Fuzzy match (exact=False) uses fnmatch
481-
await server.ahas_session("exact*", exact=False) # True
493+
>>> import asyncio
494+
>>> async def test_exact_matching():
495+
... session = await server.anew_session("exact_match_test")
496+
... # Exact match - must match full name
497+
... exact_result = await server.ahas_session("exact_match_test", exact=True)
498+
... # Partial name should not match with exact=True
499+
... partial_result = await server.ahas_session("exact", exact=True)
500+
... await server.acmd("kill-session", target="exact_match_test")
501+
... return (exact_result, partial_result)
502+
>>> asyncio.run(test_exact_matching())
503+
(True, False)
482504
"""
483505
session_check_name(target_session)
484506

@@ -635,61 +657,84 @@ async def anew_session(
635657
636658
Examples
637659
--------
638-
Sessions can be created without a session name (auto-generated IDs)::
639-
640-
session = await server.anew_session()
641-
# Session($2 2) - auto-generated name
642-
643-
session2 = await server.anew_session()
644-
# Session($3 3) - sequential IDs
645-
646-
With a custom `session_name`::
647-
648-
session = await server.anew_session(session_name='my_project')
649-
# Session($4 my_project)
660+
Sessions can be created without a session name (auto-generated IDs):
650661
651-
With custom working directory::
652-
653-
from pathlib import Path
662+
>>> import asyncio
663+
>>> async def test_auto_generated():
664+
... session1 = await server.anew_session()
665+
... session2 = await server.anew_session()
666+
... # Both have auto-generated names
667+
... has_session1 = session1.session_name is not None
668+
... has_session2 = session2.session_name is not None
669+
... # Cleanup
670+
... await asyncio.gather(
671+
... server.acmd("kill-session", target=session1.session_name),
672+
... server.acmd("kill-session", target=session2.session_name),
673+
... )
674+
... return (has_session1, has_session2)
675+
>>> asyncio.run(test_auto_generated())
676+
(True, True)
654677
655-
session = await server.anew_session(
656-
session_name='dev_session',
657-
start_directory='/tmp'
658-
)
659-
# All windows/panes will default to /tmp
678+
With a custom `session_name`:
660679
661-
With environment variables (tmux 3.2+)::
680+
>>> import asyncio
681+
>>> async def test_custom_name():
682+
... session = await server.anew_session(session_name='my_project')
683+
... name = session.session_name
684+
... await server.acmd("kill-session", target="my_project")
685+
... return name
686+
>>> asyncio.run(test_custom_name())
687+
'my_project'
662688
663-
session = await server.anew_session(
664-
session_name='env_session',
665-
environment={
666-
'PROJECT_ENV': 'development',
667-
'DEBUG': 'true'
668-
}
669-
)
670-
# Environment variables available in session
689+
With custom working directory:
671690
672-
Creating multiple sessions concurrently::
691+
>>> import asyncio
692+
>>> async def test_start_directory():
693+
... from pathlib import Path
694+
... session = await server.anew_session(
695+
... session_name='dev_session',
696+
... start_directory='/tmp'
697+
... )
698+
... # Verify session was created
699+
... exists = await server.ahas_session('dev_session')
700+
... await server.acmd("kill-session", target="dev_session")
701+
... return exists
702+
>>> asyncio.run(test_start_directory())
703+
True
673704
674-
import asyncio
705+
Creating multiple sessions concurrently:
675706
676-
sessions = await asyncio.gather(
677-
server.anew_session(session_name='frontend'),
678-
server.anew_session(session_name='backend'),
679-
server.anew_session(session_name='database'),
680-
)
681-
# All three sessions created in parallel
682-
# len(sessions) == 3
683-
# [s.session_name for s in sessions] == ['frontend', 'backend', 'database']
707+
>>> import asyncio
708+
>>> async def test_concurrent_creation():
709+
... sessions = await asyncio.gather(
710+
... server.anew_session(session_name='anew_frontend'),
711+
... server.anew_session(session_name='anew_backend'),
712+
... server.anew_session(session_name='anew_database'),
713+
... )
714+
... names = [s.session_name for s in sessions]
715+
... # Cleanup
716+
... await asyncio.gather(
717+
... server.acmd("kill-session", target="anew_frontend"),
718+
... server.acmd("kill-session", target="anew_backend"),
719+
... server.acmd("kill-session", target="anew_database"),
720+
... )
721+
... return (len(sessions), names)
722+
>>> asyncio.run(test_concurrent_creation())
723+
(3, ['anew_frontend', 'anew_backend', 'anew_database'])
684724
685-
With custom window configuration::
725+
With custom window configuration:
686726
687-
session = await server.anew_session(
688-
session_name='custom_window',
689-
window_name='main',
690-
window_command='htop'
691-
)
692-
# session.active_window.window_name == 'main'
727+
>>> import asyncio
728+
>>> async def test_custom_window():
729+
... session = await server.anew_session(
730+
... session_name='custom_window_test',
731+
... window_name='main'
732+
... )
733+
... window_name = session.active_window.window_name
734+
... await server.acmd("kill-session", target="custom_window_test")
735+
... return window_name
736+
>>> asyncio.run(test_custom_window())
737+
'main'
693738
"""
694739
if session_name is not None:
695740
session_check_name(session_name)

0 commit comments

Comments
 (0)