How To Write a Fetch Function?¶
Overview¶
The fetch() function is the entry point called by oaa-runner when a source uses a custom module. It must be defined in your custom module (e.g., modules/my_api_client.py) and is responsible for retrieving data from your source system.
Function Signature¶
Your fetch() function must accept these parameters:
def fetch(connection=None, source=None, **kwargs):
"""
Fetch the data. This is the function that is called by oaa-runner.
Args:
connection: Connection configuration object from config.yaml
- Has a `params` dict with connection-specific settings
source: Source configuration object from config.yaml
- Has a `params` dict with source-specific settings
**kwargs: Additional keyword arguments from the framework
Returns:
PETL table using lazy_dict_table() for memory-efficient processing
"""
pass
Minimal Example¶
from oaa.modules.lazy_dicts import lazy_dict_table
def fetch(connection=None, source=None, **kwargs):
"""Return a lazy-evaluated PETL table"""
def data_generator():
yield {'id': 1, 'name': 'User 1'}
yield {'id': 2, 'name': 'User 2'}
return lazy_dict_table(data_generator())
Practical Example with API Client¶
import requests
from oaa.modules.lazy_dicts import lazy_dict_table
from dotenv import load_dotenv
load_dotenv()
class MyAPIClient:
def __init__(self, base_url, api_key):
self.base_url = base_url
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Bearer {self.api_key}'
})
def fetch_records(self, resource):
"""
Fetch records from API endpoint.
Yields records one at a time for memory efficiency.
"""
url = f"{self.base_url}{resource}"
response = self.session.get(url, timeout=30)
response.raise_for_status()
data = response.json()
items = data.get('items', data.get('data', []))
# Yield one at a time to avoid loading entire dataset into memory
yield from items
def fetch(connection=None, source=None, **kwargs):
"""
Fetch the data. This is the function that is called by oaa-runner.
Returns lazy_dict_table for memory-efficient processing of large datasets.
"""
api = MyAPIClient(**connection.params)
resource = source.params.get('resource', '')
records = api.fetch_records(resource=resource)
# Use lazy_dict_table to wrap the generator with lazy evaluation
# This avoids loading all records into memory at once
return lazy_dict_table(records)
Accessing Configuration¶
Both connection and source objects have a params dictionary containing configuration from config.yaml:
def fetch(connection=None, source=None, **kwargs):
# From connections section in config.yaml
base_url = connection.params['base_url']
api_key = connection.params['api_key']
# From sources section in config.yaml
resource = source.params.get('resource', '/users')
page_size = source.params.get('page_size', 100)
# ... fetch and return data
Best Practice vs Production Reality¶
⭐ Recommended Approach (Best Practice):
from oaa.modules.lazy_dicts import lazy_dict_table
return lazy_dict_table(data_generator()) # Memory-efficient, lazy evaluation
📊 Current Production Usage (Still Valid):
import petl
return petl.fromdicts(list(data_generator())) # Works fine for smaller datasets
Why the difference?
40+ production integrations still use
petl.fromdicts()- this works well when datasets are small-to-medium (< 100K records)3 production integrations (Wolken, Zoom, Sage Intact) use
lazy_dict_table()- this is the forward-looking best practice for memory efficiency with large datasetsBoth approaches are valid; choose based on your data volume
Recommendation:
Use
lazy_dict_table()for new integrationsUse
petl.fromdicts()only if you know your data is small or have memory constraintsSee How To Handle API Pagination for memory-efficient techniques
Key Points¶
Function signature must be exactly
def fetch(connection=None, source=None, **kwargs):Import PETL operations from correct location:
from oaa.modules.lazy_dicts import lazy_dict_tableThe
fetch()function is called once per source that uses your custom moduleData should be yielded (using generators) rather than accumulated in lists:
# ✅ Good: Uses generator - memory efficient def fetch_records(resource): for item in response_data: yield item # ❌ Bad: Loads all into memory at once def fetch_records(resource): items = [] for item in response_data: items.append(item) return items
Handle errors gracefully and log important events
Production Examples¶
cloud_software_group/wolken/modules/wolken.py- Useslazy_dict_table()for memory-efficient paginationgeneric/zoom/modules/zoom.py- Useslazy_dict_table()with large user datasetsstark_technologies/sage_intact/modules/sage_intacct.py- Useslazy_dict_table()for financial data