Skip to main content

afly

CI PyPI version Python

Idempotent AppsFlyer aggregate reports → ClickHouse, with a dbt-style CLI.

afly pulls AppsFlyer's aggregate Pull API reports and writes them into ClickHouse. It's a dbt/detectkit-style project: an afly_project.yml describes the project, profiles.yml holds credentials, and extracts/*.yml declare which reports to pull and where they land.

Why

  • Idempotent by construction, not by convention. Every run rebuilds whole ClickHouse partitions atomically (REPLACE PARTITION) instead of appending — re-running the same window, or recovering from a crash mid-run, never duplicates a row and never needs FINAL on read.
  • Quota-aware. AppsFlyer enforces a per-minute limit and a small daily budget for wider date ranges. afly's scheduler interleaves apps/report types to stay under both automatically, and a backfill can be capped with --max-calls/--max-minutes and safely resumed later.
  • The Facebook split is a config concern, not a bug. AppsFlyer only returns campaign/adset/adgroup breakdown columns for a Facebook-scoped pull — afly's scaffold ships a dedicated facebook extract alongside an unfiltered one that explicitly excludes it, so the ownership is explicit instead of accidentally double-counted.
  • A --dry-run you can actually trust. Before pulling anything, afly run --dry-run prints the exact window/chunk plan and the AppsFlyer quota totals it would spend, flagging anything that would exceed budget.

Install

pip install afly

Requires Python 3.10+.

60-second quickstart

afly init my_project && cd my_project
cp .env.example .env        # fill in AppsFlyer token + ClickHouse credentials
set -a; source .env; set +a

afly validate                       # config sanity check, no network calls
afly debug                          # probe AppsFlyer/ClickHouse connectivity
afly run --select "*" --dry-run     # print the plan, pull nothing
afly run --select "*"               # pull for real
SELECT date, media_source, sum(total_cost)
FROM appsflyer.appsflyer_geo_by_date
GROUP BY 1, 2
ORDER BY 1, 2;

See Quickstart for the full walkthrough.

The three-extract picture

afly init scaffolds a realistic split that demonstrates the ownership convention every extra media-source extract should follow:

# extracts/standard.yml — everything else
name: standard
report_type: geo_by_date_report
exclude_media_sources: [Facebook Ads, yandexdirect_int]   # owned below
table: appsflyer_geo_by_date

# extracts/facebook.yml — needs its own scoped pull for adset/adgroup columns
name: facebook
media_source: facebook
table: appsflyer_geo_by_date

# extracts/yandex.yml — reports arrive late, so re-pull a wider window
name: yandex
media_source: yandexdirect_int
lookback_days: 7
table: appsflyer_geo_by_date

All three write the same table safely, because standard explicitly excludes what the other two own. See Extracts guide and Facebook split.

AI-native onboarding

afly init-claude

Scaffolds CLAUDE.md + .claude/rules/afly/ + four skills (afly-setup-project, afly-new-extract, afly-backfill, afly-debug-run) into the project, so Claude Code (or another AI assistant) can configure extracts, size a backfill against the AppsFlyer quota, and debug a failing run with the real reference instead of guessing. Idempotent — safe to re-run after upgrading afly. See Claude Code guide.

Documentation

Requirements

  • Python 3.10+
  • ClickHouse (tested against 22.11)
  • An AppsFlyer account with Pull API (API V2) access

License

MIT License — see LICENSE for details.

Release files for afly 0.1.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 afly 0.1.0
File Size Uploaded
afly-0.1.0.tar.gz 132.5 kB Details

Built distribution (wheel)

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

Total release size: 298.2 kB

Release files / afly-0.1.0.tar.gz

Download URL afly-0.1.0.tar.gz
Size 132.5 kB
Tags Source
SHA-256 checksum
How to use checksums
4bb1a76d878d208749ebc24e3b20efae84f987759ad30187fb89ad2d9212e651
BLAKE2b-256 checksum
How to use checksums
dad663ee2e2873480e77aa19062e97c0000e2d63180ca1d10c3e7cc8e5fd440d
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 24, 2026.

Transparency log

Release files / afly-0.1.0-py3-none-any.whl

Download URL afly-0.1.0-py3-none-any.whl
Size 165.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f1a47baa39091ce15c3843f698885b4b2a2105f31723d4c6962e3647b34c8676
BLAKE2b-256 checksum
How to use checksums
a77d3a5443324102ef4f38b620c89934ef7080ae14aacdbadedc390ae28005f5
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 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.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