I'm working on a bit of code right now that downloads the entire product catalog. Below us the code (in python). It uses the API library https://github.com/bolaurent/zuora_restful_python to query via Export ZOQL (which does the join for you). I'm still working on this code (I plan to copy the product catalog from one instance of zuora to another), but the part that queries and takes apart the product catalog is definitely working. It's kind of ugly that I have to name the fields explicitly. I don't see any way in REST to query the schema. You could fetch the wsdl and parse it, but that seemed like a lot of work... You said something that seemed to contrast REST API with Zoql queries. I think that way of thinking is counterproductive; you can definitely use REST to fetch data via a query. And if you use Export Zoql, you can fetch joined objects. I'll bet that's what Data Sources does, in the UI. # -*- coding: utf-8 -*-
import csv
FIELDS = {
'Product': [
'Id',
'Description',
'Discount_Schedule_Name__c',
'EffectiveEndDate',
'EffectiveStartDate',
'Name',
'SKU'
],
'ProductRatePlan': [
'Id',
'Description',
'EffectiveEndDate',
'EffectiveStartDate',
'Name'
],
'ProductRatePlanCharge': [
'Id',
'AccountingCode',
'ApplyDiscountTo',
'BillCycleDay',
'BillCycleType',
'BillingPeriod',
'BillingPeriodAlignment',
'BillingTiming',
'ChargeModel',
'ChargeType',
'DefaultQuantity',
'DeferredRevenueAccount',
'Description',
'DiscountLevel',
'EndDateCondition',
'IncludedUnits',
'ListPriceBase',
'MaxQuantity',
'MinQuantity',
'Name',
'NumberOfPeriod',
'OverageCalculationOption',
'OverageUnusedUnitsCreditOption',
'PriceChangeOption',
'PriceIncreasePercentage',
'RecognizedRevenueAccount',
'RevenueRecognitionRuleName',
'RevRecCode',
'RevRecTriggerCondition',
'SmoothingModel',
'SpecificBillingPeriod',
'Taxable',
'TaxCode',
'TaxMode',
'TriggerEvent',
'UOM',
'UpToPeriods',
'UpToPeriodsType',
'UsageRecordRatingOption',
'UseDiscountSpecificAccountingCode',
'UseTenantDefaultForPriceChange',
'WeeklyBillCycleDay'
],
'ProductRatePlanChargeTier': [
'Id',
'Currency',
'DiscountAmount',
'DiscountPercentage',
'EndingUnit',
'Price',
'PriceFormat',
'StartingUnit',
'Tier'
]
}
def split_record(record):
objects = {
'Product': {},
'ProductRatePlan': {},
'ProductRatePlanCharge': {},
'ProductRatePlanChargeTier': {}
}
for key, val in record.items():
object, field = key.split('.')
objects[object][field] = val
return objects
OBJECT_HIERARCHY = {
'ProductRatePlan': 'Product',
'ProductRatePlanCharge': 'ProductRatePlan',
'ProductRatePlanChargeTier': 'ProductRatePlanCharge'
}
class ZuoraProductCatalog(object):
def __init__(self, zuora):
self.zuora = zuora
self.records_by_id = self.parse_catalog()
def join_object_fields(self, objectname, fields):
return ', '.join(['{}.{}'.format(objectname, field) for field in fields])
def join_objects(self):
return ', '.join([self.join_object_fields(objectname, FIELDS[objectname]) for objectname in FIELDS.keys()])
def pull_catalog(self):
query = 'select {} from ProductRatePlanChargeTier'.format(self.join_objects())
query += " where Product.EffectiveEndDate >= '2018-01-01' And Product.Name = 'AWS'"
csv_data = self.zuora.query_export(query).split('\n')
records = [record for record in csv.DictReader(csv_data)]
return records
def parse_catalog(self):
records_by_id = {
'Product': {},
'ProductRatePlan': {},
'ProductRatePlanCharge': {},
'ProductRatePlanChargeTier': {}
}
for record in self.pull_catalog():
records_by_object_name = split_record(record)
for objectname in records_by_id:
obj = records_by_object_name[objectname]
id = obj['Id']
if id not in records_by_id[objectname]:
if objectname in OBJECT_HIERARCHY:
parent_object_name = OBJECT_HIERARCHY[objectname]
obj[OBJECT_HIERARCHY[objectname] + 'Id'] = records_by_object_name[parent_object_name]['Id']
records_by_id[objectname][id] = obj
return records_by_id
... View more