# Infra Core Capabilities

Use `infra-core` before writing custom HTTP retry loops, polling helpers, Azure storage glue, or ad-hoc asyncio orchestration.

If you are in a repo that depends on `infra-core`, start with:

```python
from infra_core.capabilities import CAPABILITIES, find_capabilities

for capability in find_capabilities("poll async job status"):
    print(capability.task, capability.preferred_import)
```

Install `infra-core[azure]` for `infra_core.azure` helpers.

## Task Map

| Need | Prefer | Notes |
| --- | --- | --- |
| Retried HTTP requests | `from infra_core import fetch, fetch_async, request_async` | Adds retry/backoff defaults and sanitized URL logging. |
| Poll remote job status | `from infra_core import poll_until, poll_until_async, StatusPoller` | Handles timeout, backoff, transient 404s, and sync/async fetchers. |
| Run bounded async work | `from infra_core import TaskRuntime, RuntimeConfig` | Central place for concurrency limits, cancellation, and per-task timeout handling. |
| Download files with retries | `from infra_core import download_asset, download_asset_async, AssetDownloadClient` | Reuses connections, retries transient failures, and can compute checksums. |
| Mirror files to Azure Blob | `from infra_core.azure import AzureStorageSettings, get_client` | Explicit config, sync/async blob operations, local mirroring support. |
| Build run manifests | `from infra_core.azure import build_manifest` | Generates typed artifact manifests with file metadata and blob URLs. |
| Call Azure Functions | `from infra_core.azure import AzureFunctionAsyncClient, AzureFunctionSyncClient` | Wraps HTTP calls with API-key injection and retry/timeout overrides. |
| Track jobs in Azure Table | `from infra_core.azure import AzureTableJobStore, TableConfig` | Stores per-job records, supports connection strings or `DefaultAzureCredential`, and lists runs by batch ID. |
| Emit heartbeats and run logs | `from infra_core.azure import HeartbeatMonitor, RunLogWriter, TelemetryClient` | For long-running jobs that need mirrored heartbeat/log/telemetry output. |

## Preferred Usage

- Prefer top-level `infra_core` imports for HTTP, polling, runtime, and asset-download tasks.
- Prefer `infra_core.azure` imports for Azure storage, manifests, monitoring, and job tracking.
- Prefer `TaskRuntime` over hand-rolled `asyncio.Semaphore` + timeout + cancellation loops.
- Prefer `poll_until` / `poll_until_async` over custom `while True` polling loops.
- Prefer `AzureStorageSettings.from_env()` or `.from_env_with_prefix()` over reading Azure env vars directly.

## Examples

Repository example scripts:

- [examples/http_fetch_with_retries.py](examples/http_fetch_with_retries.py)
- [examples/poll_remote_job.py](examples/poll_remote_job.py)
- [examples/bounded_async_runtime.py](examples/bounded_async_runtime.py)
- [examples/download_and_mirror_asset.py](examples/download_and_mirror_asset.py)
- [examples/azure_function_and_job_store.py](examples/azure_function_and_job_store.py)

### Poll a remote job until completion

```python
from infra_core import poll_until_async

result = await poll_until_async(
    fetch_fn=lambda: client.get_json(f"/jobs/{job_id}"),
    is_terminal=lambda response: response["status"] in {"succeeded", "failed"},
    timeout=300.0,
    poll_interval=5.0,
    backoff_multiplier=1.5,
)
```

### Run bounded concurrent async work

```python
from infra_core import RuntimeConfig, TaskRuntime

runtime = TaskRuntime(config=RuntimeConfig(concurrency=8, task_timeout=60.0))
await runtime.run(tasks)
```

### Download a file with retry and checksum

```python
from pathlib import Path
from infra_core import download_asset

result = download_asset("https://example.com/logo.png", Path("assets/logo.png"))
print(result.path, result.checksum)
```

### Upload or mirror artifacts to Azure Blob

```python
from pathlib import Path
from infra_core.azure import AzureStorageSettings, get_client

settings, mode = AzureStorageSettings.from_env_with_prefix("CRAWLER")
client = get_client(settings, mode)
client.write_json(Path("output/results.json"), {"status": "ok"})
```

### Discover the right helper programmatically

```python
from infra_core.capabilities import find_capabilities

matches = find_capabilities("azure blob manifest")
for capability in matches:
    print(capability.task, capability.preferred_import)
```
