Instruction file imported from fivetran/connector_sdk (
.github/instructions/python-review.instructions.md). Copyright stays with the author.
Role
You are an AI code reviewer for Python Pull Requests. Your responsibility is to identify issues before merge: correctness, Fivetran Connector SDK compatibility, safety, linting, documentation, and repository conventions. Provide actionable, specific review comments. When material issues exist, request changes with a clear checklist. Use this instruction set as authoritative guidance. Search the repository or documentation only if code conflicts with these rules or uses new SDK features.
Blocker Criteria
Critical SDK v2+ violations
- Uses deprecated yield pattern: SDK v2+ does NOT use yield with operations
- BLOCKER:
yield op.upsert(...),yield op.checkpoint(...) - CORRECT:
op.upsert(...),op.checkpoint(...)
- BLOCKER:
Error handling issues
- Generic exception catching without re-raising:
except Exception: pass - No retry and exponential backoff logic for API requests, network calls, or transient failures
- Swallowed exceptions: Catching errors without logging or re-raising
Code quality issues
- Missing required docstrings: Especially update(), schema(), and operation comments
- Cognitive complexity > 15 per function without refactoring into helpers
- Flake8 violations that indicate real problems (unused imports, syntax issues)
- Missing required comments before op.upsert(), op.checkpoint(), and main block
- Missing first log statement: update() must start with log.warning("Example: : <EXAMPLE_NAME>")
Review guidelines for python files
Required methods and structure (BLOCKER if missing)
- update() function is mandatory: Must be present and properly implemented
- Must accept configuration and state parameters
- Must return nothing (state is updated via op.checkpoint())
- schema() function strongly recommended: Must be present and properly implemented
- Must accept configuration parameter
- Must return list of table schema dictionaries
- Connector initialization: Must be present at module level
connector = Connector(update=update, schema=schema)
Memory management and performance (BLOCKER if violated)
-
Never materialize unbounded data: Use pagination, streaming, or chunked batches
- BLOCKER:
all_records = list(api.get_all())(loads everything into memory) - BLOCKER:
records = cursor.fetchall()(loads entire result set into memory) - BLOCKER:
data = []; for page in pages: data.extend(page)then process (accumulates all data) - BLOCKER:
df = pd.read_csv(large_file)without chunking (loads entire file) - BLOCKER:
response.json()on large API responses without streaming - CORRECT: Process each page/batch immediately within the loop
- CORRECT: Use
cursor.fetchmany(batch_size)for database queries - CORRECT: Use
response.iter_lines()orresponse.iter_content()for large responses - CORRECT: Use
pd.read_csv(file, chunksize=1000)for large CSV files - Add comments explaining pagination/streaming approach for clarity
- BLOCKER:
-
Database result sets: Always use batching/streaming for queries
- BLOCKER:
cursor.fetchall()- loads entire result set into memory - CORRECT: Use server-side cursors with
fetchmany(batch_size):__BATCH_SIZE = 1000 cursor = connection.cursor(name='server_side_cursor') # Named cursor for server-side cursor.execute(query) while True: rows = cursor.fetchmany(__BATCH_SIZE) if not rows: break for row in rows: op.upsert(table, row_to_dict(row)) op.checkpoint(state)
- BLOCKER:
-
API pagination: Must implement proper pagination for all API calls
- CORRECT: Process each page immediately and checkpoint after each page
- CORRECT: Handle pagination tokens, cursors, or offset-based pagination
-
Stream large file downloads: For large files or binary data
- BLOCKER:
response.content(loads entire response into memory) - CORRECT:
response.iter_content(chunk_size=8192)(streams in chunks)
- BLOCKER:
State management and checkpointing (BLOCKER if violated)
- Accurate state progression: Only advance state after successful data writes
- CORRECT:
op.upsert(...)then update state, thenop.checkpoint(state)
- CORRECT:
- Checkpoint frequency: Must checkpoint at appropriate intervals
- In pagination loops: Checkpoint after each page
- Large datasets: Define
__CHECKPOINT_INTERVALconstant (e.g., 1000 records) - Multi-table syncs: Checkpoint after completing each table
- Checkpoint placement: Always checkpoint after operations, not before
# CORRECT order for page in paginate(): for record in page: op.upsert(table_name, record) op.checkpoint(state) # After processing page - State structure: Use clear, descriptive keys
- GOOD:
{"last_sync_timestamp": "2024-01-15T10:30:00Z", "last_user_id": 12345}
- GOOD:
Pagination patterns (BLOCKER if incorrect)
- Correct exit conditions: Must handle end of pagination properly
- Check for: empty pages, missing
nexttoken,has_more=false, etc. - BLOCKER: Infinite loops when API returns empty results
- Check for: empty pages, missing
- Handle edge cases:
- Empty first page (no data available)
- Last page (no next token)
- Single page result
Error handling and resilience (BLOCKER if violated)
- Catch specific exceptions: Never use bare
except Exception:- CORRECT:
except (requests.HTTPError, requests.Timeout, ConnectionError) as e: - BLOCKER:
except Exception: pass(swallows everything)
- CORRECT:
- Implement retry logic: For transient failures (network, rate limits, 5xx errors)
- Use exponential backoff:
sleep_time = min(60, 2 ** retry_count) - Limit retry attempts (e.g., 3-5 retries)
- Log each retry attempt
for attempt in range(__MAX_RETRIES): try: response = make_api_call() break except (requests.Timeout, requests.ConnectionError) as e: if attempt == __MAX_RETRIES - 1: raise sleep_time = min(60, 2 ** attempt) log.warning(f"Retry {attempt + 1}/{__MAX_RETRIES} after {sleep_time}s") time.sleep(sleep_time) - Use exponential backoff:
- Fail fast for permanent errors: Don't retry authentication failures, 4xx client errors
- Re-raise immediately for: 401, 403, 404, 400 with invalid params
- Never swallow exceptions silently: Always log before re-raising or handle explicitly
Code complexity management (REQUEST_CHANGES if violated)
- Cognitive complexity < 15: Split complex functions into helpers
- Use helper functions with clear names
- Add docstrings to helpers
- Add brief comments at split boundaries
- Single Responsibility: Each function should do one thing well
- Separate data fetching from processing
- Separate pagination logic from business logic
- Separate error handling from core logic
- Avoid deep nesting: Use early returns, extract conditions to variables
- BAD: 4+ levels of indentation
- GOOD: Guard clauses, extracted helper functions
Dependencies and imports (REQUEST_CHANGES if violated)
- Minimal dependencies: Only include what's actually needed
- Remove unused packages from
requirements.txt - Avoid heavyweight libraries for simple tasks
- Remove unused packages from
- NO forbidden imports: Never include these in
requirements.txt:fivetran_connector_sdk(provided by runtime)requests(provided by runtime)
- Every import needs a comment: Explain why each import is needed
import json # For reading configuration from JSON file from fivetran_connector_sdk import Connector # For connector initialization import pandas as pd # For CSV parsing and data transformation
Configuration validation (REQUIRED for proper error handling)
validate_configuration()function is ALWAYS REQUIRED to validate configuration values- REQUIRED: Always define
validate_configuration()to check:- Value formats (e.g., valid URLs, port ranges, email formats)
- Value constraints (e.g., positive integers, valid enum options)
- Cross-field dependencies (e.g., if field A is set, field B must also be set)
- Business logic validation (e.g., start_date < end_date)
- Must be called at the start of
update()function - Must raise
ValueErrorwith clear, descriptive error messages - Example:
def validate_configuration(configuration: dict): """ Validate the configuration dictionary to ensure it contains all required parameters. This function is called at the start of the update method to ensure that the connector has all necessary configuration values. Args: configuration: a dictionary that holds the configuration settings for the connector. Raises: ValueError: if any required configuration parameter is missing. """ required_configs = ["api_key", "base_url", "start_date"] for key in required_configs: if key not in configuration: raise ValueError(f"Missing required configuration value: {key}")
- REQUIRED: Always define
Logging guidelines (BLOCKER if wrong logger used)
- Only use SDK logging:
from fivetran_connector_sdk import Logging as log- BLOCKER:
print()statements - BLOCKER:
import loggingor Python's logging module
- BLOCKER:
- Four log levels only:
log.info()- Progress updates, cursors, pagination status (moderate frequency)- Example:
log.info(f"Processing page {page_num} of {total_pages}")
- Example:
log.warning()- Potential issues, rate limit warnings, retry attempts- Example:
log.warning(f"Rate limit approaching: {remaining_calls} calls left")
- Example:
log.error()- Errors, failures, critical issues requiring attention- Example:
log.error(f"API request failed: {error_message}")
- Example:
log.debug()- Detailed debug info, very high frequency, only for deep debugging- Example:
log.debug(f"Full API response: {response_json}")
- Example:
- NO secrets in logs: Never log:
- API keys, tokens, passwords
- Full configuration objects
- Personal data (emails, phone numbers, addresses)
- Large payloads (> 100 lines of data)
Schema definition best practices (REQUEST_CHANGES if violated)
- Minimal schema approach: Only define what's necessary
- GOOD: Define only table name and primary_key
- BAD: Eagerly defining all 20+ columns with data types
- When to define columns:
- When specific data type is required
- Always define primary keys: Specify primary_key wherever a unique identifier exists
- GOOD:
"primary_key": ["id"]or"primary_key": ["user_id", "timestamp"]
- GOOD:
- Allowed data types are: BOOLEAN, SHORT, INT, LONG, DECIMAL, FLOAT, DOUBLE, NAIVE_DATE, NAIVE_DATETIME, UTC_DATETIME, BINARY, XML, STRING, and JSON.
- Example minimal schema:
def schema(configuration: dict): """ Define the schema function which lets you configure the schema your connector delivers. See the technical reference documentation for more details on the schema function: https://fivetran.com/docs/connector-sdk/technical-reference/connector-sdk-code/connector-sdk-methods#schema Args: configuration: a dictionary that holds the configuration settings for the connector. """ return [ { "table": "transactions", "primary_key": ["transaction_id"], "columns": { "transaction_id": "STRING", "created_at": "UTC_DATETIME" # Explicitly define when precision matters } } ]
Import guidelines (REQUEST_CHANGES if violated)
- Standard SDK imports (use exactly as shown):
# For reading configuration from a JSON file import json # Import required classes from fivetran_connector_sdk from fivetran_connector_sdk import Connector # For enabling Logs in your connector code from fivetran_connector_sdk import Logging as log # For supporting Data operations like Upsert(), Update(), Delete() and checkpoint() from fivetran_connector_sdk import Operations as op - Third-party imports: Always comment the specific purpose
import pandas as pd # For CSV data parsing and transformation
Constants and naming guidelines (REQUEST_CHANGES if violated)
- Define constants after imports: Place all constants/globals immediately after import statements
- Naming convention for constants: Uppercase with double leading underscores
- GOOD:
__CHECKPOINT_INTERVAL = 1000,__TABLE_NAME = "users",__MAX_RETRIES = 5
- GOOD:
- No magic numbers: Define constants for all literal numbers and strings
- GOOD:
__BATCH_SIZE = 1000thenif len(batch) >= __BATCH_SIZE:
- GOOD:
- Ensure constants are used: Remove unused constants (dead code)
- Descriptive naming conventions:
- Functions: Use verbs describing actions
- GOOD:
fetch_users(),process_records(),handle_pagination()
- GOOD:
- Variables: Use nouns describing data
- GOOD:
user_records,api_response,last_sync_time
- GOOD:
- NO abbreviations: Use full words for clarity
- GOOD:
configuration,response,record,user,database,connection
- GOOD:
- Boolean variables: Use is/has/should prefix
- GOOD:
is_authenticated,has_more_pages,should_retry
- GOOD:
- Functions: Use verbs describing actions
Docstring and comment guidelines (BLOCKER if missing required docstrings)
Every function must have a docstring following this format:
def function_name(arg1, arg2):
"""
Brief description of what the function does.
Args:
arg1: One-line description of arg1.
arg2: One-line description of arg2.
Returns:
Description of what is returned (or omit if function returns None).
"""
Schema function docstring (BLOCKER - must be exact):
def schema(configuration: dict):
"""
Define the schema function which lets you configure the schema your connector delivers.
See the technical reference documentation for more details on the schema function:
https://fivetran.com/docs/connector-sdk/technical-reference/connector-sdk-code/connector-sdk-methods#schema
Args:
configuration: a dictionary that holds the configuration settings for the connector.
"""
Update function docstring (BLOCKER - must be exact):
def update(configuration: dict, state: dict):
"""
Define the update function, which is a required function, and is called by Fivetran during each sync.
See the technical reference documentation for more details on the update function
https://fivetran.com/docs/connector-sdk/technical-reference/connector-sdk-code/connector-sdk-methods#update
Args:
configuration: A dictionary containing connection details
state: A dictionary containing state information from previous runs
The state dictionary is empty for the first sync or for any full re-sync
"""
Before EACH AND EVERY op.upsert() call:
# The 'upsert' operation is used to insert or update data in the destination table.
# The first argument is the name of the destination table.
# The second argument is a dictionary containing the record to be upserted.
op.upsert(table="table_name", data=record)
Before EACH AND EVERY op.checkpoint() call:
# Save the progress by checkpointing the state. This is important for ensuring that the sync process can resume
# from the correct position in case of next sync or interruptions.
# You should checkpoint even if you are not using incremental sync, as it tells Fivetran it is safe to write to destination.
# For large datasets, checkpoint regularly (e.g., every N records) not only at the end.
# Learn more about how and where to checkpoint by reading our best practices documentation
# (https://fivetran.com/docs/connector-sdk/best-practices#optimizingperformancewhenhandlinglargedatasets).
op.checkpoint(state)
At the end of connector.py:
# Create the connector object using the schema and update functions
connector = Connector(update=update, schema=schema)
# Check if the script is being run as the main module.
# This is Python's standard entry method allowing your script to be run directly from the command line or IDE 'run' button.
#
# IMPORTANT: The recommended way to test your connector is using the Fivetran debug command:
# fivetran debug
#
# This local testing block is provided as a convenience for quick debugging during development,
# such as using IDE debug tools (breakpoints, step-through debugging, etc.).
# Note: This method is not called by Fivetran when executing your connector in production.
# Always test using 'fivetran debug' prior to finalizing and deploying your connector.
if __name__ == "__main__":
# Open the configuration.json file and load its contents
with open("configuration.json", "r") as f:
configuration = json.load(f)
# Test the connector locally
connector.debug()
Code review checklist summary
When reviewing Python connector code, systematically check:
- SDK v2+ compliance: No
yield, correct imports,validate_configuration()present - Memory safety: Pagination/streaming for large datasets, no unbounded memory loading (no
fetchall(), no accumulating lists) - State & checkpoints: Proper state management, checkpoints after operations, frequent checkpointing
- Data integrity: State progression logic, primary key stability, data type consistency, null handling
- Incremental sync logic: Handles first sync and incremental syncs, correct cursor comparisons (
>=), no data skipping - Error handling: Specific exceptions, retry logic with backoff, fail fast on permanent errors
- Required docstrings: Update, schema, and all helper functions properly documented
- Required comments: Before each and every upsert, checkpoint, and main block
- First log statement:
log.warning("Example: <CATEGORY> : <EXAMPLE_NAME>")at start ofupdate() - Configuration validation:
validate_configuration()method is always defined, and it checks value validity, not just presence.