|
| 1 | +(asyncio)= |
| 2 | + |
| 3 | +# Async Support |
| 4 | + |
| 5 | +libvcs provides **async equivalents** for all synchronous APIs, enabling |
| 6 | +non-blocking VCS operations ideal for managing multiple repositories |
| 7 | +concurrently. |
| 8 | + |
| 9 | +## Overview |
| 10 | + |
| 11 | +The async API mirrors the sync API with an `Async` prefix: |
| 12 | + |
| 13 | +| Sync Class | Async Equivalent | |
| 14 | +|------------|------------------| |
| 15 | +| {class}`~libvcs.cmd.git.Git` | {class}`~libvcs.cmd._async.git.AsyncGit` | |
| 16 | +| {class}`~libvcs.cmd.hg.Hg` | {class}`~libvcs.cmd._async.hg.AsyncHg` | |
| 17 | +| {class}`~libvcs.cmd.svn.Svn` | {class}`~libvcs.cmd._async.svn.AsyncSvn` | |
| 18 | +| {class}`~libvcs.sync.git.GitSync` | {class}`~libvcs.sync._async.git.AsyncGitSync` | |
| 19 | +| {class}`~libvcs.sync.hg.HgSync` | {class}`~libvcs.sync._async.hg.AsyncHgSync` | |
| 20 | +| {class}`~libvcs.sync.svn.SvnSync` | {class}`~libvcs.sync._async.svn.AsyncSvnSync` | |
| 21 | + |
| 22 | +## Why Async? |
| 23 | + |
| 24 | +Async APIs excel when: |
| 25 | + |
| 26 | +- **Managing multiple repositories** - Clone/update repos concurrently |
| 27 | +- **Building responsive applications** - UI remains responsive during VCS operations |
| 28 | +- **Integration with async frameworks** - FastAPI, aiohttp, etc. |
| 29 | +- **CI/CD pipelines** - Parallel repository operations |
| 30 | + |
| 31 | +## Basic Usage |
| 32 | + |
| 33 | +### Running Git Commands |
| 34 | + |
| 35 | +```python |
| 36 | +>>> from libvcs.cmd._async.git import AsyncGit |
| 37 | +>>> async def example(): |
| 38 | +... git = AsyncGit(path=tmp_path) |
| 39 | +... await git.run(['init']) |
| 40 | +... status = await git.status() |
| 41 | +... return 'On branch' in status |
| 42 | +>>> asyncio.run(example()) |
| 43 | +True |
| 44 | +``` |
| 45 | + |
| 46 | +### Cloning a Repository |
| 47 | + |
| 48 | +```python |
| 49 | +>>> from libvcs.cmd._async.git import AsyncGit |
| 50 | +>>> async def clone_example(): |
| 51 | +... repo_path = tmp_path / 'myrepo' |
| 52 | +... git = AsyncGit(path=repo_path) |
| 53 | +... url = f'file://{create_git_remote_repo()}' |
| 54 | +... await git.clone(url=url) |
| 55 | +... return (repo_path / '.git').exists() |
| 56 | +>>> asyncio.run(clone_example()) |
| 57 | +True |
| 58 | +``` |
| 59 | + |
| 60 | +### Repository Synchronization |
| 61 | + |
| 62 | +For higher-level repository management, use the sync classes: |
| 63 | + |
| 64 | +```python |
| 65 | +>>> from libvcs.sync._async.git import AsyncGitSync |
| 66 | +>>> async def sync_example(): |
| 67 | +... url = f'file://{create_git_remote_repo()}' |
| 68 | +... repo_path = tmp_path / 'synced_repo' |
| 69 | +... repo = AsyncGitSync(url=url, path=repo_path) |
| 70 | +... await repo.obtain() # Clone |
| 71 | +... await repo.update_repo() # Pull updates |
| 72 | +... return (repo_path / '.git').exists() |
| 73 | +>>> asyncio.run(sync_example()) |
| 74 | +True |
| 75 | +``` |
| 76 | + |
| 77 | +## Concurrent Operations |
| 78 | + |
| 79 | +The primary advantage of async is running operations concurrently: |
| 80 | + |
| 81 | +```python |
| 82 | +>>> from libvcs.sync._async.git import AsyncGitSync |
| 83 | +>>> async def concurrent_clone(): |
| 84 | +... urls = [ |
| 85 | +... f'file://{create_git_remote_repo()}', |
| 86 | +... f'file://{create_git_remote_repo()}', |
| 87 | +... ] |
| 88 | +... tasks = [] |
| 89 | +... for i, url in enumerate(urls): |
| 90 | +... repo = AsyncGitSync(url=url, path=tmp_path / f'repo_{i}') |
| 91 | +... tasks.append(repo.obtain()) |
| 92 | +... await asyncio.gather(*tasks) # Clone all concurrently |
| 93 | +... return all((tmp_path / f'repo_{i}' / '.git').exists() for i in range(2)) |
| 94 | +>>> asyncio.run(concurrent_clone()) |
| 95 | +True |
| 96 | +``` |
| 97 | + |
| 98 | +## Progress Callbacks |
| 99 | + |
| 100 | +Async APIs support async progress callbacks for real-time output streaming: |
| 101 | + |
| 102 | +```python |
| 103 | +>>> import datetime |
| 104 | +>>> from libvcs._internal.async_run import async_run |
| 105 | +>>> async def with_progress(): |
| 106 | +... output_lines = [] |
| 107 | +... async def progress(output: str, timestamp: datetime.datetime) -> None: |
| 108 | +... output_lines.append(output) |
| 109 | +... result = await async_run(['echo', 'hello'], callback=progress) |
| 110 | +... return result.strip() |
| 111 | +>>> asyncio.run(with_progress()) |
| 112 | +'hello' |
| 113 | +``` |
| 114 | + |
| 115 | +For sync callbacks, use the wrapper: |
| 116 | + |
| 117 | +```python |
| 118 | +>>> from libvcs._internal.async_run import wrap_sync_callback |
| 119 | +>>> def my_sync_callback(output: str, timestamp: datetime.datetime) -> None: |
| 120 | +... print(output, end='') |
| 121 | +>>> async_callback = wrap_sync_callback(my_sync_callback) |
| 122 | +``` |
| 123 | + |
| 124 | +## Sync vs Async Comparison |
| 125 | + |
| 126 | +### Sync Pattern |
| 127 | + |
| 128 | +```python |
| 129 | +from libvcs.sync.git import GitSync |
| 130 | + |
| 131 | +repo = GitSync(url="https://github.com/user/repo", path="/tmp/repo") |
| 132 | +repo.obtain() # Blocks until complete |
| 133 | +repo.update_repo() # Blocks again |
| 134 | +``` |
| 135 | + |
| 136 | +### Async Pattern |
| 137 | + |
| 138 | +```python |
| 139 | +import asyncio |
| 140 | +from libvcs.sync._async.git import AsyncGitSync |
| 141 | + |
| 142 | +async def main(): |
| 143 | + repo = AsyncGitSync(url="https://github.com/user/repo", path="/tmp/repo") |
| 144 | + await repo.obtain() # Non-blocking |
| 145 | + await repo.update_repo() |
| 146 | + |
| 147 | +asyncio.run(main()) |
| 148 | +``` |
| 149 | + |
| 150 | +## Error Handling |
| 151 | + |
| 152 | +Async methods raise the same exceptions as sync equivalents: |
| 153 | + |
| 154 | +```python |
| 155 | +>>> from libvcs import exc |
| 156 | +>>> from libvcs._internal.async_run import async_run |
| 157 | +>>> async def error_example(): |
| 158 | +... try: |
| 159 | +... await async_run(['git', 'nonexistent-command'], check_returncode=True) |
| 160 | +... except exc.CommandError as e: |
| 161 | +... return 'error caught' |
| 162 | +... return 'no error' |
| 163 | +>>> asyncio.run(error_example()) |
| 164 | +'error caught' |
| 165 | +``` |
| 166 | + |
| 167 | +### Timeout Handling |
| 168 | + |
| 169 | +```python |
| 170 | +>>> from libvcs import exc |
| 171 | +>>> from libvcs._internal.async_run import async_run |
| 172 | +>>> async def timeout_example(): |
| 173 | +... try: |
| 174 | +... # Very short timeout for demo |
| 175 | +... await async_run(['sleep', '10'], timeout=0.1) |
| 176 | +... except exc.CommandTimeoutError: |
| 177 | +... return 'timeout caught' |
| 178 | +... return 'completed' |
| 179 | +>>> asyncio.run(timeout_example()) |
| 180 | +'timeout caught' |
| 181 | +``` |
| 182 | + |
| 183 | +## Testing with pytest-asyncio |
| 184 | + |
| 185 | +Use the async fixtures for testing: |
| 186 | + |
| 187 | +```python |
| 188 | +import pytest |
| 189 | + |
| 190 | +@pytest.mark.asyncio |
| 191 | +async def test_repo_operations(async_git_repo): |
| 192 | + # async_git_repo is an AsyncGitSync instance |
| 193 | + status = await async_git_repo.cmd.status() |
| 194 | + assert 'On branch' in status |
| 195 | +``` |
| 196 | + |
| 197 | +See {doc}`/pytest-plugin` for available async fixtures. |
| 198 | + |
| 199 | +## When to Use Async |
| 200 | + |
| 201 | +| Scenario | Recommendation | |
| 202 | +|----------|----------------| |
| 203 | +| Single repository, simple script | Sync API (simpler) | |
| 204 | +| Multiple repositories concurrently | **Async API** | |
| 205 | +| Integration with async framework | **Async API** | |
| 206 | +| CI/CD with parallel operations | **Async API** | |
| 207 | +| Interactive CLI tool | Either (prefer sync for simplicity) | |
| 208 | + |
| 209 | +## API Reference |
| 210 | + |
| 211 | +### Command Classes |
| 212 | + |
| 213 | +- {class}`~libvcs.cmd._async.git.AsyncGit` - Async git commands |
| 214 | +- {class}`~libvcs.cmd._async.hg.AsyncHg` - Async mercurial commands |
| 215 | +- {class}`~libvcs.cmd._async.svn.AsyncSvn` - Async subversion commands |
| 216 | + |
| 217 | +### Sync Classes |
| 218 | + |
| 219 | +- {class}`~libvcs.sync._async.git.AsyncGitSync` - Async git repository management |
| 220 | +- {class}`~libvcs.sync._async.hg.AsyncHgSync` - Async mercurial repository management |
| 221 | +- {class}`~libvcs.sync._async.svn.AsyncSvnSync` - Async subversion repository management |
| 222 | + |
| 223 | +### Internal Utilities |
| 224 | + |
| 225 | +- {func}`~libvcs._internal.async_run.async_run` - Low-level async command execution |
| 226 | +- {class}`~libvcs._internal.async_subprocess.AsyncSubprocessCommand` - Async subprocess wrapper |
0 commit comments