pyadmanager
A simple, modern Python REST client for the Google Ad Manager API.
Overview / Why pyadmanager?
pyadmanager is a developer-friendly way to read data out of Google Ad Manager: typed, autocomplete-able filter builders, automatic pagination and retries, and one GAMClient entry point — no code generation, no WSDL, no hand-built query strings.
For comparison, Google offers two official clients:
googleads— the legacy SOAP API client. Powerful, but heavyweight and tied to SOAP's older, more ceremonious request/response model.google-ads-admanager— the official REST API (v1) client, code-generated from the API's protobuf definitions. Complete, but as with most generated clients, it prioritizes full API coverage over an ergonomic, idiomatic-Python feel.
pyadmanager targets that same REST API but is hand-written for ergonomics: it trades full write-API coverage (it's read-only today — see Roadmap) for a small, typed surface that's easy to read, autocomplete, and reason about. If you just need to pull line items, run a report, or look up custom targeting keys through the Ad Manager REST API (v1), it does that without you needing to hand-roll requests calls or re-derive GAM's filter-query grammar (field = "value" for strings, bare true/false for booleans, RFC-3339 strings for dates) yourself.
pyadmanager wraps that REST API in a thin, typed client:
- One
GAMCliententry point, one resource client per GAM resource (.line_item,.report,.order, ...), each built lazily and cached. - Typed
listfilter keyword arguments per resource so your editor autocompletes valid filter fields andLiteralenum values (e.g.LineItemType), andpyrightcatches typos before you hit the API. - Pagination, retries (with exponential backoff on 429/500/502/503/504), and GAM's filter-string quoting rules handled for you.
- Read-only today. Every resource client currently only implements
list/get, even for resources where the REST API supports writes.create/update/deletesupport is on the roadmap — if you need write access right now, this isn't yet the library for you.
It's aimed at ad-ops engineers and backend developers who need to read Ad Manager data (line items, orders, reports, inventory) into Python — for dashboards, ETL pipelines, or one-off scripts — without pulling in the full googleads SOAP stack.
Features
- Typed, per-resource clients —
line_item,order,placement,ad_unit,custom_targeting,role,user,network,private_auction,private_auction_deal,programmatic_buyer, andreport. - Correct GAM filter-string building —
GAMRestFilterscentralizes the quoting rules (quoted strings/dates, bare numbers/booleans) so you never hand-write a malformedfilterquery param. - Automatic pagination —
listmethods page throughnextPageTokenand return the fully collected list. - Built-in retry with backoff — transient errors (429/500/502/503/504, connection/timeout errors) are retried automatically.
- Async report support —
run_report()kicks off a GAM report job and returns aReportJobyou can poll and pull rows from, optionally parsed straight into apolars.DataFrame. - Cross-resource id resolution — pass a bare
order_id/key_id/parent_ad_unit_idint and it's resolved into the correct GAM resource path for you.
Installation
Requires Python 3.13+.
pip install pyadmanager
To use ReportJob.fetch_rows_as_dataframe() / parse_report_rows(), install the optional polars extra:
pip install "pyadmanager[polars]"
Without the extra, everything else works normally — parse_report_rows just raises a clear ImportError if you call it without polars installed.
Quick Start (The 80/20 Example)
from pyadmanager import GAMClient
client = GAMClient.from_service_account_file(
network_code="123456789",
filename="creds.json",
readonly=True, # requests the read-only OAuth scope
)
# List every non-archived line item for a given order
line_items = client.line_item.list(order_id=98765, archived=False)
for item in line_items:
print(item["displayName"], item["status"])
Detailed Usage & Code Examples
Basic Example
from pyadmanager import GAMClient
client = GAMClient.from_service_account_file(
network_code="123456789",
filename="creds.json",
)
# Fetch a single line item by numeric id
line_item = client.line_item.get(line_item_id=112233)
print(line_item["name"], line_item["lineItemType"])
# List active custom targeting keys
keys = client.custom_targeting.list_keys(status="ACTIVE")
for key in keys:
print(key["displayName"])
Advanced Example
from datetime import datetime, timedelta, timezone
from pyadmanager import GAMClient
from pyadmanager.filters import GAMRestFilters
client = GAMClient.from_service_account_file(
network_code="123456789",
filename="creds.json",
)
# Filter using a (value, filter_type) tuple to build CONTAINS/GT_EQ-style clauses
one_week_ago = datetime.now(timezone.utc) - timedelta(days=7)
delivering_line_items = client.line_item.list(
display_name=("Q3", "CONTAINS"), # displayName = "*Q3*"
status=["DELIVERING", "READY"], # (status = "DELIVERING" OR status = "READY")
start_time=(one_week_ago, "GT_EQ"), # startTime >= "2026-..."
page_size=500, # tune the page size (default 1000)
)
# Run a saved report and pull results straight into a polars DataFrame
# (requires: pip install "pyadmanager[polars]")
job = client.report.run_report(report_id=445566)
df = job.fetch_rows_as_dataframe() # polls until GAM finishes computing rows
print(df.head())
# Handle report failures explicitly instead of letting fetch_rows_as_dataframe raise
try:
job.wait_till_complete(sleep=5.0)
except ValueError as exc:
print("report job failed:", exc)
else:
rows = job.fetch_rows() # raw pages, if you'd rather parse them yourself
API Overview / Key Reference
| Class / Method | Inputs | Output | Description |
|---|---|---|---|
GAMClient.from_service_account_file(network_code, filename, readonly=False, **kwargs) |
GAM network code, path to a service-account JSON key, optional readonly flag |
GAMClient |
Builds credentials from a key file on disk and returns a ready-to-use client. |
GAMClient.from_service_account_info(network_code, info, readonly=False, **kwargs) |
GAM network code, service-account key as a dict |
GAMClient |
Same as above, for key material loaded from a secrets manager rather than a file. |
GAMClient.<resource> |
— | resource client (e.g. LineItemClient) |
Lazily built, cached per-resource client sharing the parent's network code and session. |
<Resource>Client.list(**filters, page_size=1000) |
typed filter kwargs per resource | list[dict] |
Pages through every matching result via nextPageToken. |
<Resource>Client.get(id) |
numeric resource id | dict |
Fetches a single resource by id. |
ReportClient.run_report(report_id) |
numeric report id | ReportJob |
Starts async generation of a saved report's rows. |
ReportJob.wait_till_complete(sleep=2.5) |
poll interval (seconds) | None |
Blocks until the report job is done; raises ValueError on job failure. |
ReportJob.fetch_rows(sleep=2.5) |
poll interval (seconds) | list[dict] |
Polls to completion if needed, then returns raw report result pages. |
ReportJob.fetch_rows_as_dataframe(sleep=2.5) |
poll interval (seconds) | polars.DataFrame |
Same as fetch_rows, parsed into a DataFrame (requires the polars extra). |
GAMRestFilters.text_filter/number_filter/boolean_filter/date_filter/id_based_filter |
field name, value (or (value, FILTER_TYPE) tuple) |
str |
Low-level building blocks for GAM's filter-query grammar — used internally by every resource's list method. |
Configuration & Customization
- Auth scope —
from_service_account_file/from_service_account_infodefault to the fullhttps://www.googleapis.com/auth/admanagerscope; passreadonly=Trueforhttps://www.googleapis.com/auth/admanager.readonly, or an explicitscopes=[...]kwarg to override either default. - Extra credential kwargs — any additional keyword arguments passed to the
from_service_account_*constructors are forwarded directly togoogle.oauth2.service_account.Credentials. - Page size — every
listmethod acceptspage_size(default1000) to tune how many results are fetched per underlying HTTP request; pagination across pages happens automatically regardless of this value. - Report poll interval —
ReportJob.wait_till_complete/fetch_rows/fetch_rows_as_dataframeaccept asleepargument (default2.5seconds) controlling how often the async report job's status is polled. - Logging — the library logs via the standard
loggingmodule (logging.getLogger("pyadmanager...")); enableDEBUGlogging to see outgoing request URLs/params and report-job poll iterations, orINFOfor lifecycle events like credential loading and report job completion.
Roadmap
- Write support (
create/update/delete) for resources where the underlying REST API supports it (e.g.orders,lineItems,placements,adUnits). The library is read-only today; this is the main planned expansion of scope. - New resource coverage, following the existing
services/<resource>.pypattern as GAM's REST API surface grows.
Have a resource or write operation you need sooner? Open an issue — it helps prioritize the roadmap.
Contributing
Contributions are welcome! To get set up locally:
uv sync --extra polars # install all deps, including the optional polars extra
uv run pytest # run the test suite
uv run ruff check . # lint
uv run pyright src tests # type check
Please open an issue before starting on a larger change, especially write support (see Roadmap) so the approach can be agreed on before you invest the time. Bug reports and PRs for new read-only resources are especially appreciated — see services/ for the pattern each resource client follows.
License
MIT — see LICENSE for details.
Metadata
Release files for pyadmanager 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyadmanager-0.1.1.tar.gz | 21.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyadmanager-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.3 kB
Release files / pyadmanager-0.1.1.tar.gz
| Download URL | pyadmanager-0.1.1.tar.gz |
|---|---|
| Size | 21.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3c95a1a73a9218f9d2d11fab6ae5e935a019fc0cf3becd4b989021a118d18ec3
|
|
BLAKE2b-256 checksum How to use checksums |
714e0c2c6473ca5edddcc4f703c7fdb09ab16f51086effbcd12bf538213206cc
|
| 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 Aug 10, 2026.
Transparency logRelease files / pyadmanager-0.1.1-py3-none-any.whl
| Download URL | pyadmanager-0.1.1-py3-none-any.whl |
|---|---|
| Size | 33.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ec970d355e2c796a0fa68467ae538b96099bcbb79f8ab213d74b38aaaba76d6c
|
|
BLAKE2b-256 checksum How to use checksums |
39f75082ee97888c1bcabc7e269aa2ea1912fa1f2bcc2629f52a2e1b7764710d
|
| 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 Aug 10, 2026.
Transparency log