Skip to main content

azure-vm-sdk

Python SDK for managing Azure VMs via OpenTofu.

Prerequisites

  • Python 3.11+
  • uv package manager
uv sync

Usage

Single VM

from azure_vm import AzureClient

client = AzureClient(resource_group="my-rg", location="westeurope")
vm = client.launch(name="my-vm", vm_size="Standard_B2s")
print(vm.info())
vm.delete()

Multiple VMs in parallel

Use launch_many to create N VMs simultaneously. All VMs are provisioned in parallel; if any one fails the already-created VMs are destroyed automatically before the exception is re-raised (fail-fast with rollback).

from azure_vm import AzureClient, VmConfig

client = AzureClient(resource_group="my-rg", location="westeurope")

# Different names, sizes, and images
vms = client.launch_many(
    [
        VmConfig(
            name="frontend",
            vm_size="Standard_B1s",
            image_urn="Canonical:0001-com-ubuntu-server-noble:24_04-lts:latest",
        ),
        VmConfig(
            name="backend",
            vm_size="Standard_B2s",
            image_urn="Canonical:0001-com-ubuntu-server-jammy:22_04-lts:latest",
            disk_size_gb=64,
        ),
        VmConfig(
            name="db",
            vm_size="Standard_D2s_v3",
            disk_size_gb=128,
        ),
    ]
)

for vm in vms:
    print(vm.name, vm.info().ipv4)

VmConfig accepts the same keyword arguments as launch:

Field Default Description
name None (auto-generated) VM name
vm_size "Standard_B1s" Azure VM size
disk_size_gb 30 OS disk size
image_urn None (Ubuntu 24.04 LTS) Marketplace image URN
cloud_init_config None cloud-init dict or YAML string
ssh_key_path None (inherits from client) Path to SSH public key

max_workers caps the thread pool size (default: one thread per VM):

vms = client.launch_many(configs, max_workers=4)

End-to-end smoke test

Provision a real Azure VM, wait for SSH readiness, run a verification command, and tear it down. Requires Azure credentials and OpenTofu installed.

# Prerequisites
az login
export AZURE_RESOURCE_GROUP=my-resource-group
export AZURE_LOCATION=westeurope
export AZURE_SSH_PUBLIC_KEY=~/.ssh/id_rsa.pub   # optional

# Run with defaults (single VM)
uv run azure-vm-e2e

# Custom VM size and image
uv run azure-vm-e2e --name my-test-vm --vm-size Standard_D2s_v3 \
    --image-urn "Canonical:0001-com-ubuntu-server-noble:24_04-lts:latest" \
    --timeout 300

# Create 3 identical VMs in parallel (names: worker-0, worker-1, worker-2)
uv run azure-vm-e2e --count 3 --name worker --vm-size Standard_B2s

# Create VMs with different names, sizes, and images
uv run azure-vm-e2e --configs '[
  {"name": "frontend", "vm_size": "Standard_B1s"},
  {"name": "backend",  "vm_size": "Standard_B2s", "disk_size_gb": 64},
  {"name": "db",       "vm_size": "Standard_D2s_v3", "disk_size_gb": 128}
]'

Output shows a 5-step progress:

[1/5] Launching VM 'e2e-1747152000' (size=Standard_B1s) ...
       launch completed in 52.3s
[2/5] Waiting for public IP (timeout=300s) ...
       got IP 20.1.2.3 in 18.5s
[3/5] Waiting for SSH on 20.1.2.3:22 ...
       SSH ready on 20.1.2.3 in 42.1s
[4/5] Running verification command ...
       exit=0  stdout=Linux e2e-1747152000 6.8.0-1020-azure ...
       state=running  location=westeurope  resource_group=my-rg
[5/5] Deleting VM 'e2e-1747152000' ...
       done.

SUCCESS — VM 'e2e-1747152000' completed full lifecycle.

Running tests

# Unit tests only (default, no Azure/OpenTofu needed)
uv run pytest

# Include integration tests (requires Azure + OpenTofu installed).
# --no-cov: the 80% coverage gate targets the unit suite, so it would fail on
# an integration-only run.
uv run pytest -m "integration" --no-cov

# Coverage report (already on by default, with an 80% gate; shown explicitly)
uv run pytest --cov=azure_vm --cov-report=term-missing

Quality checks

# Run all three (ruff + basedpyright + import-linter)
uv run azure-vm-quality

Individual checks:

uv run ruff check .                    # Linting (pycodestyle, pyflakes, isort, bugbear, pyupgrade, pydocstyle, …)
uv run ruff format .                   # Formatting
uv run basedpyright                    # Type checking
uv run bandit -c pyproject.toml -r src # Security static analysis
uv run lint-imports                    # Architecture contract enforcement
uv run pre-commit run --all-files      # Every hook above, all at once

Code evaluation tools

Full audit

Runs automated detection for bugs, excessive coupling, simplification opportunities, code smells, and god classes.

uv run azure-vm-eval           # Human-readable report
uv run azure-vm-eval --json    # JSON output (for CI / machine consumption)

Checks performed:

Category What it detects
Bugs Bare/broad except, raise without from, unused exception classes, mutable defaults
Coupling High fan-out modules, circular dependencies, zero fan-in modules
Simplifications Functions over 30 lines, duplicated logic
Smells God classes (lines, methods, fan-out), modules over 250 lines, __init__ leaking internals

Package cohesion report

Measures internal cohesion and inter-module coupling for every module in azure_vm.

uv run azure-vm-package-report           # Cohesion/coupling table
uv run azure-vm-package-report --edges   # Include individual import edges
uv run azure-vm-package-report --orphans # Show modules with zero internal deps

Metrics:

Column Meaning
internal Imports within the same module
outgoing Imports from other azure_vm modules
incoming Times this module is imported by other azure_vm modules
external Imports from third-party packages
instability outgoing / (incoming + outgoing) — 0 = highly stable (depended on by many), 1 = highly unstable (depends on everything)

Dependency graph visualization

uv run pydeps azure_vm --show-deps --max-bacon 2 --nodot

Rendering the diagram itself additionally needs Graphviz (dot) on PATH; --nodot skips that step and prints the dependency analysis as JSON.

Architecture contracts

Defined in .importlinter:

  1. models_and_exceptions_are_independent — foundation layer must not import higher-level packages
  2. backend_is_independent — _backend must not depend on vm or client
  3. internal_modules_are_independent — _templates, _workspace, _discovery must not depend on the public API layer

Project structure

src/azure_vm/
├── __init__.py        Public API re-exports
├── client.py          AzureClient — VM provisioning orchestrator
├── vm.py              AzureVM — single-VM operations (lifecycle, SSH, exec)
├── _workspace.py      Workspace — filesystem, template writing, scan, purge
├── _discovery.py      Azure Marketplace image discovery
├── _templates.py      HCL generation + image URN / SSH path helpers
├── _backend.py        CommandBackend protocol, TofuBackend, FakeBackend, run_command
├── models.py          VmConfig, VmInfo, VmState, ImageInfo
├── exceptions.py      Exception hierarchy
└── testing.py         Test doubles for consumers (FakeBackend, CommandResult)

Release files for azure-vm-sdk 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for azure-vm-sdk 0.6.0
File Size Uploaded
azure_vm_sdk-0.6.0.tar.gz 133.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for azure-vm-sdk 0.6.0
File Interpreter ABI Platform
azure_vm_sdk-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 168.0 kB

Release files / azure_vm_sdk-0.6.0.tar.gz

Download URL azure_vm_sdk-0.6.0.tar.gz
Size 133.8 kB
Tags Source
SHA-256 checksum
How to use checksums
b22bdaab69cbe8abdd8fba0fa0ad94a3fb37ab796839119ba360048ebd82204c
BLAKE2b-256 checksum
How to use checksums
577571d6acc4ba54d0085e35646117f1141bd67be3463732879e47ff43d809c9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / azure_vm_sdk-0.6.0-py3-none-any.whl

Download URL azure_vm_sdk-0.6.0-py3-none-any.whl
Size 34.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5c12b8c91ceb3559e3d74b2006c75222f14e6ab1ef25d96c28d450d28abce0c8
BLAKE2b-256 checksum
How to use checksums
69047ed887c5a268f0c97f6201891c0905d59dcf8dda70a46c012f47860f9cc5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page