Skip to content

feature(meta_lead_ads): add Meta Lead Ads connector example - #69

Open
fivetran-tanishk wants to merge 2 commits into
mainfrom
feature/meta-lead-ads-connector
Open

fivetran-tanishk wants to merge 2 commits into
mainfrom
feature/meta-lead-ads-connector

Conversation

@fivetran-tanishk

@fivetran-tanishk fivetran-tanishk commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a new community connector that syncs Facebook (Meta) Lead Ads data into a single leads table via the Meta Graph API.
  • Supports discovering all pages/forms or restricting to explicit page_ids/form_ids, per-form incremental sync on created_time, and threshold-based checkpointing to bound replay volume on failure.
  • Adds the connector to the root README.md connector list.

Test plan

  • python -m py_compile passes on all connector files.
  • black --line-length 99 and flake8 (repo .flake8 config) pass with zero findings.

🤖 Generated with Claude Code

Syncs Facebook (Meta) Lead Ads data into a single leads table using
the Graph API, with per-form incremental sync and threshold-based
checkpointing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@fivetran-tanishk
fivetran-tanishk requested a review from a team September 18, 2026 12:23
Match the repo's Code Quality CI check, which runs black with the
99-char line length used across this repo instead of black's default.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

API failures can silently truncate syncs, and mid-pagination timestamp checkpoints can permanently skip leads.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 5 High severity · 8 Medium severity · 23 Low severity

Open (36)

And 16 more that still need to be addressed.

What changed in this PR

Adds a Meta Lead Ads connector example with Graph API discovery, incremental lead syncing, and checkpointing.

Changes:

  • Adds connector logic, API helpers, retries, validation, and configuration.
  • Documents setup, authentication, schema, and behavior.
  • Registers the connector in the repository catalog.
File Description
README.md Adds the connector listing.
meta_lead_ads/​README.md Documents the connector.
meta_lead_ads/​configuration.json Defines configuration values.
meta_lead_ads/​connector.py Implements syncing and checkpointing.
meta_lead_ads/​http_helpers.py Implements HTTP retries.
meta_lead_ads/​meta_helpers.py Implements Graph API pagination and discovery.
meta_lead_ads/​validator.py Validates and normalizes configuration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +89 to +93
if rows_since_checkpoint >= cfg["check_point_limit"]:
_write_checkpoint(
form_id=form_id,
forms_state=forms_state,
current_cursor=current_cursor,
Comment on lines +21 to +22
Returns the decoded JSON body on success; returns None after exhausting retries.
Callers must handle the None case (critical failures).
Comment on lines +31 to +32
except requests.RequestException as e:
log.warning(f"Network error (attempt {attempt}/{__MAX_ATTEMPTS}) url={url} error={e}")
Comment on lines +50 to +52
if resp.status_code != 200:
log.severe(f"Non-success status {resp.status_code} url={url} body={resp.text[:300]}")
return None
Comment on lines +59 to +62
def _convert_time_to_unix(timestr: str) -> int:
"""Convert a Graph API-formatted UTC timestamp string to a Unix epoch timestamp."""
dt = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S+0000")
return int(dt.timestamp())
def _paginate_graph_collection(
path: str, params: Dict[str, Any] | None, cfg: Dict[str, Any]
) -> Iterator[Dict[str, Any]]:
"""Generator that walks Graph API paging via the `next` cursor."""
Comment on lines +3 to +5
from __future__ import annotations
from datetime import datetime
from typing import Dict, Any, List, Optional
)

def _list(key: str) -> Optional[List[str]]:
"""Parse a comma-separated configuration value into an ID list, or None for 'ALL'."""


def validate_page_access_tokens(pages: List[Dict[str, Any]]) -> None:
"""Raise if any discovered page is missing an access token needed to sync its forms."""
minimum: Optional[int] = None,
maximum: Optional[int] = None,
) -> int:
"""Parse a configuration value as an integer, applying a default and range checks."""

@fivetran-satvikpatil fivetran-satvikpatil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fivetran-tanishk please address the copilot comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants