Skip to main content

pyado — Python bindings for the Azure DevOps REST API

PyPI Status Python Version License Read the Docs Tests Coverage Ruff uv

pyado — Python bindings for the Azure DevOps REST API

Typing SVG

Typed, Pydantic-backed wrappers and Pythonic convenience methods for the Azure DevOps REST API — no raw dicts, no string parsing, full IDE completion.


pyado wraps the Azure DevOps REST API at two levels so you can pick the right abstraction for the job.

The raw layer (pyado.raw) is a thin, one-function-per-endpoint mapping of the ADO REST surface. Every function accepts an ApiCall credential object and one or more fully-typed Pydantic request models, then returns a fully-typed Pydantic response model. Bad inputs are caught at model construction time, before any HTTP request is ever issued. Pagination is transparent: list endpoints return plain Python generators that fetch the next page automatically when the iterator advances.

The OOP layer (pyado.oop, also re-exported at the top-level pyado) builds on top of the raw layer and exposes every ADO resource as a Python object. AzureDevOpsService is the single entry point; from there you navigate a strict ownership hierarchy down to repositories, pull requests, work items, builds, pipelines, variable groups, teams, and classification nodes. Objects cache their data lazily on first access and share identity across paths — fetching build.project and calling org.get_project("MyProject") both return the exact same Project instance. Authentication, retries, connection pooling, and content-type negotiation are handled entirely by the framework.


Common operations

import pyado

# Credentials come from env vars if not passed explicitly:
#   AZURE_DEVOPS_ORG (or SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)
#   AZURE_DEVOPS_EXT_PAT
svc = pyado.AzureDevOpsService(org="https://dev.azure.com/myorg", pat="<pat>")
proj = svc.org.get_project("MyProject")

Work items

# Fetch and update a work item
wi = proj.boards.get_work_item(153)
print(wi.title, wi.state)
wi.update({"System.State": "Resolved"})
wi.add_tag("reviewed")
wi.add_comment("Confirmed in staging.", comment_format="markdown")

# Query with WIQL
for wi in proj.boards.iter_work_items(
    "SELECT [System.Id] FROM WorkItems WHERE [System.State] = 'Active'"
):
    print(wi.id, wi.title)

# Create a work item
wi = proj.boards.create_work_item(
    "Task",
    fields={
        "System.Title": "Investigate memory leak",
        "System.AssignedTo": "jane@example.com",
    },
)

Pull requests

repo = proj.repos.get_repository("myrepo")

# Create — branch names are normalised automatically
pr = repo.create_pull_request(
    title="Deploy v2.1",
    source_branch="feature/v2",
    target_branch="main",
    description="Promotes the v2 feature branch.",
)
pr.add_reviewer(reviewer_id, is_required=True)
pr.add_label("ready-to-merge")
pr.link_work_item(wi)  # shows on both the PR page and the work item

# List all active PRs across every repo in the project
for pr in proj.repos.iter_active_prs():
    print(pr.repo.name, pr.title, pr.status)

# Complete a PR
pr.enable_auto_complete()
pr.complete(last_merge_source_commit=pr.info.last_merge_source_commit.commit_id)

Builds and pipelines

# Queue a build
pipeline = proj.pipelines.get_pipeline("deploy-prod")
build = proj.pipelines.start_build(pipeline, source_branch="refs/heads/main")
print(build.id, build.number, build.status)

# Inspect stages, jobs, and tasks
for stage in build.iter_stages():
    for job in stage.iter_jobs():
        for task in job.iter_tasks():
            print(f"  {task.name}: {task.result}")

# Trigger a YAML pipeline run with template parameters
run = pipeline.start_run(template_parameters={"env": "staging"})

# Approve a pending environment gate
for approval in proj.pipelines.iter_approvals():
    proj.pipelines.approve(approval.id, comment="LGTM")

Repositories and file commits

# Read file content — no local git clone required
text = repo.get_file_at_branch("/config.json", "main")

# Push changes programmatically
result = repo.commit(
    "main",
    "chore: update config",
    [
        pyado.EditFile("/config.json", '{"key": "value"}'),
        pyado.DeleteFile("/old_config.json"),
        pyado.AddFile("/new_file.txt", "hello"),
    ],
)
print(result.commits[0].commit_id)

# Branches and tags
repo.create_branch("feature/new-branch", from_commit="abc123")
repo.create_tag("v1.2.3", "abc123")
for tag in proj.repos.iter_git_tags("myrepo"):
    print(tag.name, tag.commit_id)

Variable groups

vg = proj.pipelines.library.get_variable_group("my-secrets")

# Read-modify-write — safe for concurrent callers
vg.set_variable("API_KEY", "new-value")
vg.set_variable("API_SECRET", "s3cr3t", is_secret=True)
vg.delete_variable("DEPRECATED_KEY")
vg.refresh()

Teams and iterations

team = proj.boards.get_team("Backend Team")
for sprint in team.iter_sprint_iterations():
    print(sprint.name, sprint.attributes.start_date)

for member in team.iter_members():
    print(member.identity.display_name)

Service hooks

# List all webhook subscriptions
for sub in org.iter_hook_subscriptions():
    print(sub.publisher_id, sub.event_type, sub.consumer_id)

# Create a webhook that fires on every completed build
from pyado.raw import HookSubscriptionCreateRequest

org.create_hook_subscription(
    HookSubscriptionCreateRequest(
        publisher_id="tfs",
        event_type="build.complete",
        resource_version="1.0",
        consumer_id="webHooks",
        consumer_action_id="httpRequest",
        publisher_inputs={"projectId": "<project-id>"},
        consumer_inputs={"url": "https://hooks.example.com/ado"},
    )
)

Task groups

for tg in proj.pipelines.iter_task_groups():
    print(tg.name, tg.description)

tg = proj.pipelines.get_task_group("my-deploy-steps")

See the full usage guide for all domains and the raw API.


Authentication

pyado resolves credentials from three sources, checked in order:

Source Argument Environment variable
Personal access token pat="<token>" AZURE_DEVOPS_EXT_PAT
Organisation URL org="https://dev.azure.com/myorg" AZURE_DEVOPS_ORG or SYSTEM_TEAMFOUNDATIONCOLLECTIONURI
Azure identity credential=DefaultAzureCredential() (any azure-identity flow)
# From environment variables — useful in CI/CD (SYSTEM_ACCESSTOKEN works too)
svc = pyado.AzureDevOpsService()

# Azure managed identity or workload identity federation
from azure.identity import DefaultAzureCredential

svc = pyado.AzureDevOpsService(
    org="https://dev.azure.com/myorg",
    credential=DefaultAzureCredential(),
)

The underlying requests.Session is LRU-cached per access token, so constructing multiple ApiCall objects with the same token all share a single connection pool — no reconnect overhead.


What you get

  • Full type safety. Every function accepts and returns Pydantic models. Bad inputs — wrong URL scheme, missing required field, invalid UUID — are caught at construction time with a clear validation error, not buried inside an HTTP 400 response long after the call was made. IDE completion works on every field of every request and response model.

  • No boilerplate. Authentication (PAT or Azure identity), session management (LRU-cached connection pools keyed on the token), automatic retries on transient connection resets, and content-type negotiation (JSON vs JSON Patch vs octet-stream) are handled transparently. Every call site looks the same.

  • Automatic pagination. Every list endpoint returns a plain Python generator. Page boundaries, $skip/$top bookkeeping, and the ADO diff endpoint's allChangesIncluded stop flag are managed internally. Write a for loop, get all items.

  • Optimistic concurrency for git operations. pyado reads the current HEAD SHA before every push and passes it as old_object_id, so concurrent pushes to the same branch cannot silently overwrite each other — ADO rejects the later write, and the caller retries with the updated SHA.

  • Pythonic convenience methods. Push a commit without touching git internals. Create a PR and attach work items in two lines. Fetch all log output for a build in a single call. Manage tags on work items as plain Python lists, with case-insensitive deduplication matching ADO's own normalisation.

  • Shared object identity. The OOP service deduplicates resource objects by identity: build.project is wi.project is guaranteed when both objects belong to the same project, regardless of how they were fetched. Back navigation (.project, .repo, .org) is always zero-cost.

  • Everything covered. Work items, pull requests, git repositories, builds, YAML pipeline runs, variable groups, teams, iterations and areas, wikis, dashboards, policies, search, environments and deployment approvals, agent pools and queues, secure files, service endpoints, service hooks, task groups, work process templates, notification subscriptions, and the full distributed task plane API for pipeline task callbacks.


Installation

$ pip install pyado

or with uv:

$ uv add pyado

Requires Python 3.11 and an Azure DevOps personal access token (PAT). The azure-identity package is optional; install it only if you need managed identity or federated workload authentication:

$ pip install pyado[azure-identity]

Is pyado right for you?

Microsoft also publishes an official azure-devops Python package. See the alternatives comparison for a side-by-side overview to help you decide which package fits your use case.


Further reading


Contributing

Contributions are very welcome. See the Contributor Guide for details.

License

Distributed under the terms of the MIT license. pyado is free and open source software.

Issues

Please file an issue with a detailed description of the problem.

Metadata

Release files for pyado 0.10.2

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

Source distribution (sdist)

Source distribution for pyado 0.10.2
File Size Uploaded
pyado-0.10.2.tar.gz 166.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyado 0.10.2
File Interpreter ABI Platform
pyado-0.10.2-py3-none-any.whl Python 3 none any Details

Total release size: 383.5 kB

Release files / pyado-0.10.2.tar.gz

Download URL pyado-0.10.2.tar.gz
Size 166.2 kB
Tags Source
SHA-256 checksum
How to use checksums
1c7a9d9701e5b9ea7409c8aaa829199f794009f99687420fdf0f28a014fe92f1
BLAKE2b-256 checksum
How to use checksums
e905d821911130417197a89d8308dc166289605317211726df8eb9a3077d13f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 29, 2026.

Transparency log

Release files / pyado-0.10.2-py3-none-any.whl

Download URL pyado-0.10.2-py3-none-any.whl
Size 217.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4e1f623dc9d5536a8719c4d870c56200218c192cafe3309bb76bde26440ac32b
BLAKE2b-256 checksum
How to use checksums
5c97b9aff74b9a8ded4416f8a0cfb11a9ee03b27f2e288ad5beb4bfcc9231265
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 29, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.10.2 This release

2 release files

0.10.1

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.17

2 release files

0.1.16

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.1

2 release files

0.0.0

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