Overview

Through the PA Engine API, request analytics for multi-asset class performance, attribution, and risk, including the flexibility of choosing your portfolio, benchmark, return period, and more.

API Definition

swagger_file_format

API Documentation

SDK Library

Code Snippet

Building and running a calculation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using FactSet.AnalyticsAPI.PAEngineAPI.v1.Api;
using FactSet.AnalyticsAPI.PAEngineAPI.v1.Client;
using FactSet.AnalyticsAPI.PAEngineAPI.v1.Model;

namespace FactSet.AnalyticsAPI.PAEngineAPI.v1.Examples
{
    class Program
    {
        private static Configuration configuration;

        private static Configuration GetConfiguration()
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            if (configuration != null)
            {
                return configuration;
            }

            configuration = new Configuration
            {
                BasePath = "https://api.factset.com",
                Username = "<username-serial>",
                Password = "<apiKey>"
            };

            return configuration;
        }

        static void Main(string[] args)
        {
            var componentsApi = new ComponentsApi(GetConfiguration());

            // PA_DOCUMENTS:DEFAULT is default document available to all users.
            ApiResponse<Dictionary<string, ComponentListEntity>> componentGetAllResponse =
                componentsApi.GetAllWithHttpInfo("PA_DOCUMENTS:DEFAULT");
            var componentId = componentGetAllResponse.Data.First().Key;

            var account = "BENCH:SP50";
            var benchmark = "BENCH:R.1000";

            CalculationParameters inputBody = new CalculationParameters(componentId, account, benchmark);
            var calculationsApi = new CalculationsApi(GetConfiguration());
            var createrCalculationResponse = calculationsApi.CreateWithHttpInfo(inputBody);

            if (createrCalculationResponse.StatusCode != 202)
            {
                LogError(createrCalculationResponse);
                return;
            }

            // Parse the the Location header for calculation Id
            createrCalculationResponse.Headers.TryGetValue("Location", out string location);
            if (string.IsNullOrWhiteSpace(location))
            {
                LogError(createrCalculationResponse);
                return;
            }

            var splits = location.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            var calculationId = splits.Last();
            ApiResponse<object> getCalculationStatus = null;
            while (getCalculationStatus == null || getCalculationStatus.StatusCode == 202)
            {
                getCalculationStatus = calculationsApi.GetStatusByIdWithHttpInfo(calculationId);
                if (getCalculationStatus.StatusCode != 202)
                {
                    continue;
                }

                if (getCalculationStatus.Headers.ContainsKey("Cache-Control"))
                {
                    Console.WriteLine("Progress: " + getCalculationStatus.Headers["x-factset-api-pickup-progress"]);

                    var ageValue = getCalculationStatus.Headers["Cache-Control"];
                    if (string.IsNullOrWhiteSpace(ageValue))
                    {
                        Console.WriteLine("Sleeping for 2 seconds");
                        // Sleep for atleast 2 seconds.
                        Thread.Sleep(2000);
                    }
                    else
                    {
                        int age = int.Parse(ageValue.Replace("max-age=", ""));
                        Console.WriteLine($"Sleeping for {age} seconds");
                        Thread.Sleep(age * 1000);
                    }
                }
            }

            if (getCalculationStatus.StatusCode != 201)
            {
                LogError(getCalculationStatus);
                return;
            }

            var getCalculationResult = calculationsApi.GetResultByIdWithHttpInfo(calculationId);
            if (getCalculationResult.StatusCode != 200)
            {
                Console.WriteLine("Error!!!");
                Console.WriteLine("Status Code: " + getCalculationResult.StatusCode);
                Console.WriteLine("Request Key: " + getCalculationResult.Headers["X-DataDirect-Request-Key"]);
                return;
            }

            var package = getCalculationResult.Data;
            Console.WriteLine(package.ToString());
            Console.ReadKey();
        }

        private static void LogError(ApiResponse<object> response)
        {
            Console.WriteLine("Error!!!");
            Console.WriteLine("Status Code: " + response.StatusCode);
            Console.WriteLine("Request Key: " + response.Headers["X-DataDirect-Request-Key"]);
        }
    }
}

 

import java.util.List;
import java.util.Map;

import com.factset.protobuf.stach.PackageProto.Package;

import factset.analyticsapi.paengineapi.v1.ApiClient;
import factset.analyticsapi.paengineapi.v1.ApiException;
import factset.analyticsapi.paengineapi.v1.ApiResponse;
import factset.analyticsapi.paengineapi.v1.api.CalculationsApi;
import factset.analyticsapi.paengineapi.v1.api.ComponentsApi;
import factset.analyticsapi.paengineapi.v1.models.*;

public class Example {
  private static ApiClient apiClient = null;

  public static void main(String[] args) throws InterruptedException {
    try {
      ComponentsApi componentsApi = new ComponentsApi(apiClientFactory());
      Map<String, ComponentListEntity> components = componentsApi.getAll("PA_DOCUMENTS:DEFAULT");
      
      // Select required componentId from the above components list. In this example, we are using the first componentId.
      String componentId = components.entrySet().iterator().next().getKey();
      System.out.println("ComponentID: " + componentId);

      CalculationParameters parameters = new CalculationParameters();
      parameters.componentid(componentId);
      parameters.setAccount("BENCH:SP50");
      parameters.setBenchmark("BENCH:R.2000");

      CalculationsApi calculationsApi = new CalculationsApi(apiClientFactory());
      ApiResponse<Void> calculationResult = calculationsApi.createWithHttpInfo(parameters);

      Map<String, List<String>> headers = calculationResult.getHeaders();
      String locationToPoll = headers.get("Location").get(0);
      String[] splits = locationToPoll.split("/");
      String requestId = splits[splits.length - 1];

      ApiResponse<Void> calculationPollResponse = calculationsApi.getStatusByIdWithHttpInfo(requestId);

      while (calculationPollResponse.getStatusCode() == 202) {
        List<String> progress = calculationPollResponse.getHeaders().get("x-factset-api-pickup-progress");
        if (progress != null) {
          System.out.println("Progress: " + progress.get(0));
        }

        List<String> cacheControl = calculationPollResponse.getHeaders().get("cache-control");
        if (cacheControl != null) {
          int maxAge = Integer.parseInt(cacheControl.get(0).replaceAll("max-age=", ""));
          System.out.println("Sleeping for: " + maxAge + " seconds");
          Thread.sleep(maxAge * 1000);
        } else {
          System.out.println("Sleeping for: 2 seconds");
          Thread.sleep(2 * 1000);
        }

        calculationPollResponse = calculationsApi.getStatusByIdWithHttpInfo(requestId);
      }

      if (calculationPollResponse.getStatusCode() != 201) {
        System.err.println("Error!!!");
        System.err.println(
            "x-datadirect-request-key: " + calculationPollResponse.getHeaders().get("x-datadirect-request-key").get(0));
        return;
      }

      ApiResponse<Package> calculationResultResponse = calculationsApi.getResultByIdWithHttpInfo(requestId);
      // System.out.println(calculationResultResponse.getData());
      List<TableData> tables = StachExtensions.convertToTableFormat(calculationResultResponse.getData()); // To convert result to 2D tables.
      System.out.println(tables.get(0)); // Prints the result in 2D table format.
      // StachExtensions.generateExcel(calculationResultResponse.getData()); // To get the result in table format exported to excel file.

    } catch (ApiException e) {
      System.err.println("ResponseBody: " + e.getResponseBody());
      System.err.println("x-datadirect-request-key: " + e.getResponseHeaders().get("x-datadirect-request-key").get(0));
      e.printStackTrace();
    }
  }

  private static ApiClient apiClientFactory() {
    if (apiClient != null) {
      return apiClient;
    }

    apiClient = new ApiClient();
    apiClient.setBasePath("https://api.factset.com");
    apiClient.setUsername("username-serial"); // username-serial combination
    apiClient.setPassword("apiKey"); // api key

    apiClient.setVerifyingSsl(false);

    return apiClient;
  }
}

 

import time

from google.protobuf.json_format import MessageToJson
from google.protobuf.json_format import MessageToDict

from stach_extensions import StachExtensions
from fds.analyticsapi.paengineapi.v1.api_client import ApiClient
from fds.analyticsapi.paengineapi.v1.configuration import Configuration
from fds.analyticsapi.paengineapi.v1.api.components_api import ComponentsApi
from fds.analyticsapi.paengineapi.v1.api.calculations_api import CalculationsApi
from fds.analyticsapi.paengineapi.v1.models.calculation_parameters import CalculationParameters

config = Configuration()
config.host = "https://api.factset.com"
config.username = "username-serial" # username-serial combination
config.password = "apiKey" # api key
config.verify_ssl = False

api_client = ApiClient(config)

componentsApi = ComponentsApi(api_client)
component_api_get_result = componentsApi.get_all_with_http_info(documentname="PA_DOCUMENTS:DEFAULT")

# Select required componentId from the above components list. In this example, we are using the first componentId.
componentId = list(component_api_get_result[0].keys())[0]
print("Component ID: " + componentId)

calculation_params = CalculationParameters(componentId, "BENCH:SP50", "BENCH:R.2000")
calculationsApi = CalculationsApi(api_client)
calculation_api_post_result =  calculationsApi.create_with_http_info(parameters=calculation_params)

if(calculation_api_post_result[1] != 202):
    print("x-datadirect-request-key: " + calculation_api_post_result[2].get('x-datadirect-request-key'))
    quit()

statusLocation = calculation_api_post_result[2].get('location')
splits = statusLocation.split('/')
calculationId = splits[-1]
print("Calculation Id: " + calculationId)

calculation_status_result = calculationsApi.get_status_by_id_with_http_info(calculationId)
while calculation_status_result[1] == 202:
    progress = calculation_status_result[2].get('x-factset-api-pickup-progress')
    if(progress != None):
        print("Progress: " + progress)
    ageValue = calculation_status_result[2].get('cache-control')
    if(ageValue != None):
        maxAge = ageValue.replace("max-age=", "")
        print("Sleeping for: " + maxAge)
        time.sleep(int(maxAge))
    calculation_status_result = calculationsApi.get_status_by_id_with_http_info(calculationId)

if(calculation_status_result[1] != 201):
    print("x-datadirect-request-key: " + calculation_api_post_result[2].get('x-datadirect-request-key'))
    quit()

calculation_api_result = calculationsApi.get_result_by_id_with_http_info(calculationId)
# print(calculation_api_result[0])

if(calculation_api_result[1] != 200):
    print("x-datadirect-request-key: " + calculation_api_result[2].get('x-datadirect-request-key'))
    quit()

# print(MessageToJson(calculation_api_result[0])) # To print the result object as a JSON.
# print(MessageToDict(calculation_api_result[0])) # To print the result object as a Dictionary.
tables = StachExtensions.convert_to_table_format(calculation_api_result[0]) # To convert result to 2D tables.
print(tables[0]) # Prints the result in 2D table format.
# StachExtensions.generate_excel(calculation_api_result[0]) # To get the result in table format exported to excel file.

 

Converting API output to Table format
using System;
using System.Collections.Generic;
using System.Linq;
using FactSet.Protobuf.Stach;
using FactSet.Protobuf.Stach.Table;

namespace FactSet.AnalyticsAPI.PAEngineAPI.v1.Examples
{
    public static class StachExtensions
    {
        public static void GenerateCSV(this Package package)
        {
            foreach (var table in package.ConvertToTableFormat())
            {
                System.IO.File.WriteAllText($"{Guid.NewGuid():N}.csv", table.ToString());
            }
        }

        public static List<Table> ConvertToTableFormat(this Package package)
        {
            var tables = new List<Table>();
            foreach (var primaryTableId in package.PrimaryTableIds)
            {
                tables.Add(GenerateTable(package, primaryTableId));
            }

            return tables;
        }

        private static Table GenerateTable(Package package, string primaryTableId)
        {
            var primaryTable = package.Tables[primaryTableId];
            var headerId = primaryTable.Definition.HeaderTableId;
            var headerTable = package.Tables[headerId];
            var columnIds = primaryTable.Definition.Columns.Select(c => c.Id).ToList();
            var headerColumnIds = headerTable.Definition.Columns.Select(c => c.Id).ToList();
            var dimensionColumnsCount = primaryTable.Definition.Columns.Count(c => c.IsDimension);
            var rowCount = primaryTable.Data.Rows.Count;
            var headerRowCount = headerTable.Data.Rows.Count;

            var table = new Table
            {
                Rows = new List<Row>()
            };
            // Constructs the column headers by considering dimension columns and header rows
            foreach (var columnId in headerColumnIds)
            {
                var headerRow = new Row { Cells = new List<string>() };
                for (int j = 0; j < dimensionColumnsCount; j++)
                {
                    headerRow.Cells.Add("");
                }

                for (int i = 0; i < headerRowCount; i++)
                {
                    headerRow.Cells.Add(Convert.ToString(headerTable.Data.Columns[columnId]
                        .GetValueHelper(headerTable.Definition.Columns.First(c => c.Id == columnId).Type, i)));
                }
                table.Rows.Add(headerRow);
            }
            // Constructs the column data
            for (int i = 0; i < rowCount; i++)
            {
                var dataRow = new Row { Cells = new List<string>() };
                foreach (var columnId in columnIds)
                {
                    dataRow.Cells.Add(Convert.ToString(primaryTable.Data.Columns[columnId]
                        .GetValueHelper(primaryTable.Definition.Columns.First(c => c.Id == columnId).Type, i)));
                }
                table.Rows.Add(dataRow);
            }

            return table;
        }
    }

    public static class SeriesDataHelper
    {
        public static object GetValueHelper(this SeriesData seriesData, DataType dataType, int index)
        {
            switch (dataType)
            {
                case DataType.Bool:
                {
                    return seriesData.BoolArray?.Values?[index];
                }
                case DataType.Double:
                {
                    return seriesData.DoubleArray?.Values?[index];
                }
                case DataType.Duration:
                {
                    var v = seriesData.DurationArray?.Values?[index];
                    return v?.ToTimeSpan();
                }
                case DataType.Float:
                {
                    return seriesData.FloatArray?.Values?[index];
                }
                case DataType.Int32:
                {
                    return seriesData.Int32Array?.Values?[index];
                }
                case DataType.Int64:
                {
                    return seriesData.Int64Array?.Values?[index];
                }
                case DataType.String:
                {
                    return seriesData.StringArray?.Values?[index];
                }
                case DataType.Timestamp:
                {
                    var v = seriesData.TimestampArray?.Values?[index];
                    return v?.ToDateTime();
                }
                default:
                    throw new NotImplementedException($"{dataType} is not implemented");
            }
        }
    }

    public class Table
    {
        public List<Row> Rows { get; set; }

        public override string ToString()
        {
            return string.Join(Environment.NewLine, Rows);
        }
    }

    public class Row
    {
        public List<string> Cells { get; set; }

        public override string ToString()
        {
            return string.Join(",", Cells.Select(c => c.Replace(",", "")));
        }
    }
}

 

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.poi.ss.formula.eval.NotImplementedException;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.factset.protobuf.stach.NullValues;
import com.factset.protobuf.stach.PackageProto.Package;
import com.factset.protobuf.stach.table.DataTypeProto.DataType;
import com.factset.protobuf.stach.table.SeriesDataProto.SeriesData;
import com.factset.protobuf.stach.table.SeriesDefinitionProto.SeriesDefinition;
import com.factset.protobuf.stach.table.TableProto.Table;

import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;

public class StachExtensions {
  // The purpose of this class is to provide the helper methods for converting stach to Tabular format.

  public static void generateExcel(Package packageObj) {
    for (TableData table : convertToTableFormat(packageObj)) {
      writeDataToExcel(table, UUID.randomUUID().toString() + ".xlsx");
    }
  }

  public static List<TableData> convertToTableFormat(Package packageObj) {
    List<TableData> tables = new ArrayList<TableData>();
    for (String primaryTableId : packageObj.getPrimaryTableIdsList()) {
      tables.add(generateTable(packageObj, primaryTableId));
    }
    return tables;
  }

  private static TableData generateTable(Package packageObj, String primaryTableId) {
    Map<String, Table> tablesMap = packageObj.getTablesMap();
    Table primaryTable = tablesMap.get(primaryTableId);
    String headerId = primaryTable.getDefinition().getHeaderTableId();
    Table headerTable = tablesMap.get(headerId);
    int headerRowCount = headerTable.getData().getRowsCount();
    int rowsCount = primaryTable.getData().getRowsCount();

    TableData table = new TableData();

    // Construct the column headers by considering dimension columns and header rows.
    List<SeriesDefinition> headerTableSeriesDefinitions = headerTable.getDefinition().getColumnsList();
    List<SeriesDefinition> primaryTableSeriesDefinitions = primaryTable.getDefinition().getColumnsList();

    Map<String, SeriesData> headerTableColumns = headerTable.getData().getColumnsMap();
    Map<String, SeriesData> primaryTableColumns = primaryTable.getData().getColumnsMap();

    for (SeriesDefinition headerTableseriesDefinition : headerTableSeriesDefinitions) {
      Row headerRow = new Row();
      for (SeriesDefinition primaryTableSeriesDefinition : primaryTableSeriesDefinitions) {
        if (primaryTableSeriesDefinition.getIsDimension()) {
          headerRow.Cells.add(primaryTableSeriesDefinition.getDescription());
        }
      }

      String headerColumnId = headerTableseriesDefinition.getId();
      String nullFormat = headerTableseriesDefinition.getFormat().getNullFormat();
      for (int i = 0; i < headerRowCount; i++) {
        headerRow.Cells.add(SeriesDataHelper.getValueHelper(headerTableColumns.get(headerColumnId),
            headerTableseriesDefinition.getType(), i, nullFormat).toString());
      }
      table.Rows.add(headerRow);
    }

    // Construct the column data
    for (int i = 0; i < rowsCount; i++) {
      Row dataRow = new Row();
      for (SeriesDefinition primaryTableSeriesDefinition : primaryTableSeriesDefinitions) {
        String nullFormat = primaryTableSeriesDefinition.getFormat().getNullFormat();
        String primaryTableColumnId = primaryTableSeriesDefinition.getId();
        dataRow.Cells.add(SeriesDataHelper.getValueHelper(primaryTableColumns.get(primaryTableColumnId),
            primaryTableSeriesDefinition.getType(), i, nullFormat).toString());
      }
      table.Rows.add(dataRow);
    }
    return table;
  }

  private static void writeDataToExcel(TableData table, String fileLocation) {
    XSSFWorkbook workbook = new XSSFWorkbook();
    XSSFSheet sheet = workbook.createSheet("PA Report");

    int rowsSize = table.Rows.size();
    for (int rowIndex = 0; rowIndex < rowsSize; rowIndex++) {
      XSSFRow xsswRow = sheet.createRow(rowIndex);
      List<String> cells = table.Rows.get(rowIndex).Cells;
      for (int cellIndex = 0; cellIndex < cells.size(); cellIndex++) {
        XSSFCell xssfCell = xsswRow.createCell(cellIndex);
        xssfCell.setCellValue(cells.get(cellIndex));
      }
    }

    try {
      FileOutputStream out = new FileOutputStream(new File(fileLocation));
      workbook.write(out);
      out.close();
      workbook.close();
    } catch (Exception e) {
      System.err.println("Failed to write data to excel");
      e.printStackTrace();
    }
  }
}

class SeriesDataHelper {
  public static Object getValueHelper(SeriesData seriesData, DataType dataType, int index, String nullFormat) {
    if (dataType == DataType.STRING) {
      String value = seriesData.getStringArray().getValues(index);
      return NullValues.STRING.equals(value) ? nullFormat : value;
    } else if (dataType == DataType.DOUBLE) {
      double value = seriesData.getDoubleArray().getValues(index);
      return Double.isNaN(value) ? nullFormat : value;
    } else if (dataType == DataType.BOOL) {
      return seriesData.getBoolArray().getValues(index);
    } else if (dataType == DataType.DURATION) {
      Duration value = seriesData.getDurationArray().getValues(index);
      return NullValues.DURATION.equals(value) ? nullFormat : value;
    } else if (dataType == DataType.FLOAT) {
      float value = seriesData.getFloatArray().getValues(index);
      return Float.isNaN(value) ? nullFormat : value;
    } else if (dataType == DataType.INT32) {
      int value = seriesData.getInt32Array().getValues(index);
      return NullValues.INT32 == value ? nullFormat : value;
    } else if (dataType == DataType.INT64) {
      long value = seriesData.getInt64Array().getValues(index);
      return NullValues.INT64 == value ? nullFormat : value;
    } else if (dataType == DataType.TIMESTAMP) {
      Timestamp value = seriesData.getTimestampArray().getValues(index);
      return NullValues.TIMESTAMP.equals(value) ? nullFormat : value;
    } else {
      throw new NotImplementedException(dataType + " is not implemented");
    }
  }
}

class TableData {
  List<Row> Rows = new ArrayList<Row>();
  
  public String toString() {
    return Rows.toString();
  }
}

class Row {
  List<String> Cells = new ArrayList<String>();

  public String toString() {
    return Cells.toString();
  }
}

 

import uuid
import math

import pandas as pd

from fds.protobuf.stach.Package_pb2 import Package
from fds.protobuf.stach.NullValues import NullValues
from fds.protobuf.stach.table.DataType_pb2 import DataType

class StachExtensions:
    """The purpose of this class is to provide the helper methods for converting Stach to Tabular format"""

    @staticmethod
    def generate_excel(package):
        for table in StachExtensions.convert_to_table_format(package):
            writer = pd.ExcelWriter(str(uuid.uuid1()) + ".xlsx")
            table.to_excel(excel_writer=writer)
            writer.save()
            writer.close()

    @staticmethod
    def convert_to_table_format(package):
        tables = list()
        for primary_table_id in package.primary_table_ids:
            tables.append(StachExtensions.generate_table(package, primary_table_id))
        return tables

    @staticmethod
    def generate_table(package_response, primary_table_id):

        if isinstance(package_response, Package):
            primary_table = package_response.tables[primary_table_id]
            header_id = primary_table.definition.header_table_id
            header_table = package_response.tables[header_id]
            dimension_columns = list(filter(lambda column_obj: column_obj.is_dimension, primary_table.definition.columns))
            dimension_columns_count = len(dimension_columns)
            row_count = len(primary_table.data.rows)
            header_row_count = len(header_table.data.rows)

            headers = list(list())
            # Constructs the column headers by considering dimension columns and header rows
            for series_definition_column in header_table.definition.columns:
                header_row = list()
                for i in range(0, dimension_columns_count, 1):
                    header_row.append(dimension_columns[i].description)

                for i in range(0, header_row_count, 1):
                    header_row.append(str(SeriesDataHelper.get_value_helper(header_table.data.columns[series_definition_column.id], series_definition_column.type, i, series_definition_column.format.null_format)))
                headers.append(header_row)

            data = list(list())
            # Constructs the column data
            for i in range(0, row_count, 1):
                data_row = list()
                for series_definition_column in primary_table.definition.columns:
                    data_row.append(str(SeriesDataHelper.get_value_helper(primary_table.data.columns[series_definition_column.id], series_definition_column.type, i, series_definition_column.format.null_format)))
                data.append(data_row)

            data_frame = pd.DataFrame(data=data)
            data_frame.columns = pd.MultiIndex.from_arrays(headers)
            return data_frame

        else:
            ValueError("Response data passed should be of package type.")


class SeriesDataHelper:

    @staticmethod
    def get_value_helper(series_data, datatype, index, null_format):
        if DataType.Name(datatype) == "STRING":
            return SeriesDataHelper.null_value_handler(datatype, series_data.string_array.values[index], null_format)
        elif DataType.Name(datatype) == "DOUBLE":
            return SeriesDataHelper.null_value_handler(datatype, series_data.double_array.values[index], null_format)
        elif DataType.Name(datatype) == "FLOAT":
            return SeriesDataHelper.null_value_handler(datatype, series_data.float_array.values[index], null_format)
        elif DataType.Name(datatype) == "INT32":
            return SeriesDataHelper.null_value_handler(datatype, series_data.int32_array.values[index], null_format)
        elif DataType.Name(datatype) == "INT64":
            return SeriesDataHelper.null_value_handler(datatype, series_data.int64_array.values[index], null_format)
        elif DataType.Name(datatype) == "BOOL":
            return SeriesDataHelper.null_value_handler(datatype, series_data.bool_array.values[index], null_format)
        elif DataType.Name(datatype) == "DURATION":
            return SeriesDataHelper.null_value_handler(datatype, series_data.duration_array.values[index], null_format)
        elif DataType.Name(datatype) == "TIMESTAMP":
            return SeriesDataHelper.null_value_handler(datatype, series_data.timestamp_array.values[index], null_format)
        else:
            ValueError("The datatype is not implemented")

    @staticmethod
    def null_value_handler(datatype, value, null_format):
        if DataType.Name(datatype) == "STRING":
            if NullValues.STRING == value:
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "DOUBLE":
            if math.isnan(value):
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "FLOAT":
            if math.isnan(value):
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "INT32":
            if NullValues.INT32 == value:
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "INT64":
            if NullValues.INT64 == value:
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "DURATION":
            if NullValues.DURATION.equals(value):
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "TIMESTAMP":
            if NullValues.TIMESTAMP.equals(value):
                return null_format
            else:
                return value
        else:
            return value

 

Building and running a calculation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using FactSet.AnalyticsAPI.PAEngineAPI.v1.Api;
using FactSet.AnalyticsAPI.PAEngineAPI.v1.Client;
using FactSet.AnalyticsAPI.PAEngineAPI.v1.Model;

namespace FactSet.AnalyticsAPI.PAEngineAPI.v1.Examples
{
    class Program
    {
        private static Configuration configuration;

        private static Configuration GetConfiguration()
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            if (configuration != null)
            {
                return configuration;
            }

            configuration = new Configuration
            {
                BasePath = "https://api.factset.com",
                Username = "<username-serial>",
                Password = "<apiKey>"
            };

            return configuration;
        }

        static void Main(string[] args)
        {
            var componentsApi = new ComponentsApi(GetConfiguration());

            // PA_DOCUMENTS:DEFAULT is default document available to all users.
            ApiResponse<Dictionary<string, ComponentListEntity>> componentGetAllResponse =
                componentsApi.GetAllWithHttpInfo("PA_DOCUMENTS:DEFAULT");
            var componentId = componentGetAllResponse.Data.First().Key;

            var account = "BENCH:SP50";
            var benchmark = "BENCH:R.1000";

            CalculationParameters inputBody = new CalculationParameters(componentId, account, benchmark);
            var calculationsApi = new CalculationsApi(GetConfiguration());
            var createrCalculationResponse = calculationsApi.CreateWithHttpInfo(inputBody);

            if (createrCalculationResponse.StatusCode != 202)
            {
                LogError(createrCalculationResponse);
                return;
            }

            // Parse the the Location header for calculation Id
            createrCalculationResponse.Headers.TryGetValue("Location", out string location);
            if (string.IsNullOrWhiteSpace(location))
            {
                LogError(createrCalculationResponse);
                return;
            }

            var splits = location.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            var calculationId = splits.Last();
            ApiResponse<object> getCalculationStatus = null;
            while (getCalculationStatus == null || getCalculationStatus.StatusCode == 202)
            {
                getCalculationStatus = calculationsApi.GetStatusByIdWithHttpInfo(calculationId);
                if (getCalculationStatus.StatusCode != 202)
                {
                    continue;
                }

                if (getCalculationStatus.Headers.ContainsKey("Cache-Control"))
                {
                    Console.WriteLine("Progress: " + getCalculationStatus.Headers["x-factset-api-pickup-progress"]);

                    var ageValue = getCalculationStatus.Headers["Cache-Control"];
                    if (string.IsNullOrWhiteSpace(ageValue))
                    {
                        Console.WriteLine("Sleeping for 2 seconds");
                        // Sleep for atleast 2 seconds.
                        Thread.Sleep(2000);
                    }
                    else
                    {
                        int age = int.Parse(ageValue.Replace("max-age=", ""));
                        Console.WriteLine($"Sleeping for {age} seconds");
                        Thread.Sleep(age * 1000);
                    }
                }
            }

            if (getCalculationStatus.StatusCode != 201)
            {
                LogError(getCalculationStatus);
                return;
            }

            var getCalculationResult = calculationsApi.GetResultByIdWithHttpInfo(calculationId);
            if (getCalculationResult.StatusCode != 200)
            {
                Console.WriteLine("Error!!!");
                Console.WriteLine("Status Code: " + getCalculationResult.StatusCode);
                Console.WriteLine("Request Key: " + getCalculationResult.Headers["X-DataDirect-Request-Key"]);
                return;
            }

            var package = getCalculationResult.Data;
            Console.WriteLine(package.ToString());
            Console.ReadKey();
        }

        private static void LogError(ApiResponse<object> response)
        {
            Console.WriteLine("Error!!!");
            Console.WriteLine("Status Code: " + response.StatusCode);
            Console.WriteLine("Request Key: " + response.Headers["X-DataDirect-Request-Key"]);
        }
    }
}

 

import java.util.List;
import java.util.Map;

import com.factset.protobuf.stach.PackageProto.Package;

import factset.analyticsapi.paengineapi.v1.ApiClient;
import factset.analyticsapi.paengineapi.v1.ApiException;
import factset.analyticsapi.paengineapi.v1.ApiResponse;
import factset.analyticsapi.paengineapi.v1.api.CalculationsApi;
import factset.analyticsapi.paengineapi.v1.api.ComponentsApi;
import factset.analyticsapi.paengineapi.v1.models.*;

public class Example {
  private static ApiClient apiClient = null;

  public static void main(String[] args) throws InterruptedException {
    try {
      ComponentsApi componentsApi = new ComponentsApi(apiClientFactory());
      Map<String, ComponentListEntity> components = componentsApi.getAll("PA_DOCUMENTS:DEFAULT");
      
      // Select required componentId from the above components list. In this example, we are using the first componentId.
      String componentId = components.entrySet().iterator().next().getKey();
      System.out.println("ComponentID: " + componentId);

      CalculationParameters parameters = new CalculationParameters();
      parameters.componentid(componentId);
      parameters.setAccount("BENCH:SP50");
      parameters.setBenchmark("BENCH:R.2000");

      CalculationsApi calculationsApi = new CalculationsApi(apiClientFactory());
      ApiResponse<Void> calculationResult = calculationsApi.createWithHttpInfo(parameters);

      Map<String, List<String>> headers = calculationResult.getHeaders();
      String locationToPoll = headers.get("Location").get(0);
      String[] splits = locationToPoll.split("/");
      String requestId = splits[splits.length - 1];

      ApiResponse<Void> calculationPollResponse = calculationsApi.getStatusByIdWithHttpInfo(requestId);

      while (calculationPollResponse.getStatusCode() == 202) {
        List<String> progress = calculationPollResponse.getHeaders().get("x-factset-api-pickup-progress");
        if (progress != null) {
          System.out.println("Progress: " + progress.get(0));
        }

        List<String> cacheControl = calculationPollResponse.getHeaders().get("cache-control");
        if (cacheControl != null) {
          int maxAge = Integer.parseInt(cacheControl.get(0).replaceAll("max-age=", ""));
          System.out.println("Sleeping for: " + maxAge + " seconds");
          Thread.sleep(maxAge * 1000);
        } else {
          System.out.println("Sleeping for: 2 seconds");
          Thread.sleep(2 * 1000);
        }

        calculationPollResponse = calculationsApi.getStatusByIdWithHttpInfo(requestId);
      }

      if (calculationPollResponse.getStatusCode() != 201) {
        System.err.println("Error!!!");
        System.err.println(
            "x-datadirect-request-key: " + calculationPollResponse.getHeaders().get("x-datadirect-request-key").get(0));
        return;
      }

      ApiResponse<Package> calculationResultResponse = calculationsApi.getResultByIdWithHttpInfo(requestId);
      // System.out.println(calculationResultResponse.getData());
      List<TableData> tables = StachExtensions.convertToTableFormat(calculationResultResponse.getData()); // To convert result to 2D tables.
      System.out.println(tables.get(0)); // Prints the result in 2D table format.
      // StachExtensions.generateExcel(calculationResultResponse.getData()); // To get the result in table format exported to excel file.

    } catch (ApiException e) {
      System.err.println("ResponseBody: " + e.getResponseBody());
      System.err.println("x-datadirect-request-key: " + e.getResponseHeaders().get("x-datadirect-request-key").get(0));
      e.printStackTrace();
    }
  }

  private static ApiClient apiClientFactory() {
    if (apiClient != null) {
      return apiClient;
    }

    apiClient = new ApiClient();
    apiClient.setBasePath("https://api.factset.com");
    apiClient.setUsername("username-serial"); // username-serial combination
    apiClient.setPassword("apiKey"); // api key

    apiClient.setVerifyingSsl(false);

    return apiClient;
  }
}

 

import time

from google.protobuf.json_format import MessageToJson
from google.protobuf.json_format import MessageToDict

from stach_extensions import StachExtensions
from fds.analyticsapi.paengineapi.v1.api_client import ApiClient
from fds.analyticsapi.paengineapi.v1.configuration import Configuration
from fds.analyticsapi.paengineapi.v1.api.components_api import ComponentsApi
from fds.analyticsapi.paengineapi.v1.api.calculations_api import CalculationsApi
from fds.analyticsapi.paengineapi.v1.models.calculation_parameters import CalculationParameters

config = Configuration()
config.host = "https://api.factset.com"
config.username = "username-serial" # username-serial combination
config.password = "apiKey" # api key
config.verify_ssl = False

api_client = ApiClient(config)

componentsApi = ComponentsApi(api_client)
component_api_get_result = componentsApi.get_all_with_http_info(documentname="PA_DOCUMENTS:DEFAULT")

# Select required componentId from the above components list. In this example, we are using the first componentId.
componentId = list(component_api_get_result[0].keys())[0]
print("Component ID: " + componentId)

calculation_params = CalculationParameters(componentId, "BENCH:SP50", "BENCH:R.2000")
calculationsApi = CalculationsApi(api_client)
calculation_api_post_result =  calculationsApi.create_with_http_info(parameters=calculation_params)

if(calculation_api_post_result[1] != 202):
    print("x-datadirect-request-key: " + calculation_api_post_result[2].get('x-datadirect-request-key'))
    quit()

statusLocation = calculation_api_post_result[2].get('location')
splits = statusLocation.split('/')
calculationId = splits[-1]
print("Calculation Id: " + calculationId)

calculation_status_result = calculationsApi.get_status_by_id_with_http_info(calculationId)
while calculation_status_result[1] == 202:
    progress = calculation_status_result[2].get('x-factset-api-pickup-progress')
    if(progress != None):
        print("Progress: " + progress)
    ageValue = calculation_status_result[2].get('cache-control')
    if(ageValue != None):
        maxAge = ageValue.replace("max-age=", "")
        print("Sleeping for: " + maxAge)
        time.sleep(int(maxAge))
    calculation_status_result = calculationsApi.get_status_by_id_with_http_info(calculationId)

if(calculation_status_result[1] != 201):
    print("x-datadirect-request-key: " + calculation_api_post_result[2].get('x-datadirect-request-key'))
    quit()

calculation_api_result = calculationsApi.get_result_by_id_with_http_info(calculationId)
# print(calculation_api_result[0])

if(calculation_api_result[1] != 200):
    print("x-datadirect-request-key: " + calculation_api_result[2].get('x-datadirect-request-key'))
    quit()

# print(MessageToJson(calculation_api_result[0])) # To print the result object as a JSON.
# print(MessageToDict(calculation_api_result[0])) # To print the result object as a Dictionary.
tables = StachExtensions.convert_to_table_format(calculation_api_result[0]) # To convert result to 2D tables.
print(tables[0]) # Prints the result in 2D table format.
# StachExtensions.generate_excel(calculation_api_result[0]) # To get the result in table format exported to excel file.

 

Converting API output to Table format
using System;
using System.Collections.Generic;
using System.Linq;
using FactSet.Protobuf.Stach;
using FactSet.Protobuf.Stach.Table;

namespace FactSet.AnalyticsAPI.PAEngineAPI.v1.Examples
{
    public static class StachExtensions
    {
        public static void GenerateCSV(this Package package)
        {
            foreach (var table in package.ConvertToTableFormat())
            {
                System.IO.File.WriteAllText($"{Guid.NewGuid():N}.csv", table.ToString());
            }
        }

        public static List<Table> ConvertToTableFormat(this Package package)
        {
            var tables = new List<Table>();
            foreach (var primaryTableId in package.PrimaryTableIds)
            {
                tables.Add(GenerateTable(package, primaryTableId));
            }

            return tables;
        }

        private static Table GenerateTable(Package package, string primaryTableId)
        {
            var primaryTable = package.Tables[primaryTableId];
            var headerId = primaryTable.Definition.HeaderTableId;
            var headerTable = package.Tables[headerId];
            var columnIds = primaryTable.Definition.Columns.Select(c => c.Id).ToList();
            var headerColumnIds = headerTable.Definition.Columns.Select(c => c.Id).ToList();
            var dimensionColumnsCount = primaryTable.Definition.Columns.Count(c => c.IsDimension);
            var rowCount = primaryTable.Data.Rows.Count;
            var headerRowCount = headerTable.Data.Rows.Count;

            var table = new Table
            {
                Rows = new List<Row>()
            };
            // Constructs the column headers by considering dimension columns and header rows
            foreach (var columnId in headerColumnIds)
            {
                var headerRow = new Row { Cells = new List<string>() };
                for (int j = 0; j < dimensionColumnsCount; j++)
                {
                    headerRow.Cells.Add("");
                }

                for (int i = 0; i < headerRowCount; i++)
                {
                    headerRow.Cells.Add(Convert.ToString(headerTable.Data.Columns[columnId]
                        .GetValueHelper(headerTable.Definition.Columns.First(c => c.Id == columnId).Type, i)));
                }
                table.Rows.Add(headerRow);
            }
            // Constructs the column data
            for (int i = 0; i < rowCount; i++)
            {
                var dataRow = new Row { Cells = new List<string>() };
                foreach (var columnId in columnIds)
                {
                    dataRow.Cells.Add(Convert.ToString(primaryTable.Data.Columns[columnId]
                        .GetValueHelper(primaryTable.Definition.Columns.First(c => c.Id == columnId).Type, i)));
                }
                table.Rows.Add(dataRow);
            }

            return table;
        }
    }

    public static class SeriesDataHelper
    {
        public static object GetValueHelper(this SeriesData seriesData, DataType dataType, int index)
        {
            switch (dataType)
            {
                case DataType.Bool:
                {
                    return seriesData.BoolArray?.Values?[index];
                }
                case DataType.Double:
                {
                    return seriesData.DoubleArray?.Values?[index];
                }
                case DataType.Duration:
                {
                    var v = seriesData.DurationArray?.Values?[index];
                    return v?.ToTimeSpan();
                }
                case DataType.Float:
                {
                    return seriesData.FloatArray?.Values?[index];
                }
                case DataType.Int32:
                {
                    return seriesData.Int32Array?.Values?[index];
                }
                case DataType.Int64:
                {
                    return seriesData.Int64Array?.Values?[index];
                }
                case DataType.String:
                {
                    return seriesData.StringArray?.Values?[index];
                }
                case DataType.Timestamp:
                {
                    var v = seriesData.TimestampArray?.Values?[index];
                    return v?.ToDateTime();
                }
                default:
                    throw new NotImplementedException($"{dataType} is not implemented");
            }
        }
    }

    public class Table
    {
        public List<Row> Rows { get; set; }

        public override string ToString()
        {
            return string.Join(Environment.NewLine, Rows);
        }
    }

    public class Row
    {
        public List<string> Cells { get; set; }

        public override string ToString()
        {
            return string.Join(",", Cells.Select(c => c.Replace(",", "")));
        }
    }
}

 

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.poi.ss.formula.eval.NotImplementedException;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.factset.protobuf.stach.NullValues;
import com.factset.protobuf.stach.PackageProto.Package;
import com.factset.protobuf.stach.table.DataTypeProto.DataType;
import com.factset.protobuf.stach.table.SeriesDataProto.SeriesData;
import com.factset.protobuf.stach.table.SeriesDefinitionProto.SeriesDefinition;
import com.factset.protobuf.stach.table.TableProto.Table;

import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;

public class StachExtensions {
  // The purpose of this class is to provide the helper methods for converting stach to Tabular format.

  public static void generateExcel(Package packageObj) {
    for (TableData table : convertToTableFormat(packageObj)) {
      writeDataToExcel(table, UUID.randomUUID().toString() + ".xlsx");
    }
  }

  public static List<TableData> convertToTableFormat(Package packageObj) {
    List<TableData> tables = new ArrayList<TableData>();
    for (String primaryTableId : packageObj.getPrimaryTableIdsList()) {
      tables.add(generateTable(packageObj, primaryTableId));
    }
    return tables;
  }

  private static TableData generateTable(Package packageObj, String primaryTableId) {
    Map<String, Table> tablesMap = packageObj.getTablesMap();
    Table primaryTable = tablesMap.get(primaryTableId);
    String headerId = primaryTable.getDefinition().getHeaderTableId();
    Table headerTable = tablesMap.get(headerId);
    int headerRowCount = headerTable.getData().getRowsCount();
    int rowsCount = primaryTable.getData().getRowsCount();

    TableData table = new TableData();

    // Construct the column headers by considering dimension columns and header rows.
    List<SeriesDefinition> headerTableSeriesDefinitions = headerTable.getDefinition().getColumnsList();
    List<SeriesDefinition> primaryTableSeriesDefinitions = primaryTable.getDefinition().getColumnsList();

    Map<String, SeriesData> headerTableColumns = headerTable.getData().getColumnsMap();
    Map<String, SeriesData> primaryTableColumns = primaryTable.getData().getColumnsMap();

    for (SeriesDefinition headerTableseriesDefinition : headerTableSeriesDefinitions) {
      Row headerRow = new Row();
      for (SeriesDefinition primaryTableSeriesDefinition : primaryTableSeriesDefinitions) {
        if (primaryTableSeriesDefinition.getIsDimension()) {
          headerRow.Cells.add(primaryTableSeriesDefinition.getDescription());
        }
      }

      String headerColumnId = headerTableseriesDefinition.getId();
      String nullFormat = headerTableseriesDefinition.getFormat().getNullFormat();
      for (int i = 0; i < headerRowCount; i++) {
        headerRow.Cells.add(SeriesDataHelper.getValueHelper(headerTableColumns.get(headerColumnId),
            headerTableseriesDefinition.getType(), i, nullFormat).toString());
      }
      table.Rows.add(headerRow);
    }

    // Construct the column data
    for (int i = 0; i < rowsCount; i++) {
      Row dataRow = new Row();
      for (SeriesDefinition primaryTableSeriesDefinition : primaryTableSeriesDefinitions) {
        String nullFormat = primaryTableSeriesDefinition.getFormat().getNullFormat();
        String primaryTableColumnId = primaryTableSeriesDefinition.getId();
        dataRow.Cells.add(SeriesDataHelper.getValueHelper(primaryTableColumns.get(primaryTableColumnId),
            primaryTableSeriesDefinition.getType(), i, nullFormat).toString());
      }
      table.Rows.add(dataRow);
    }
    return table;
  }

  private static void writeDataToExcel(TableData table, String fileLocation) {
    XSSFWorkbook workbook = new XSSFWorkbook();
    XSSFSheet sheet = workbook.createSheet("PA Report");

    int rowsSize = table.Rows.size();
    for (int rowIndex = 0; rowIndex < rowsSize; rowIndex++) {
      XSSFRow xsswRow = sheet.createRow(rowIndex);
      List<String> cells = table.Rows.get(rowIndex).Cells;
      for (int cellIndex = 0; cellIndex < cells.size(); cellIndex++) {
        XSSFCell xssfCell = xsswRow.createCell(cellIndex);
        xssfCell.setCellValue(cells.get(cellIndex));
      }
    }

    try {
      FileOutputStream out = new FileOutputStream(new File(fileLocation));
      workbook.write(out);
      out.close();
      workbook.close();
    } catch (Exception e) {
      System.err.println("Failed to write data to excel");
      e.printStackTrace();
    }
  }
}

class SeriesDataHelper {
  public static Object getValueHelper(SeriesData seriesData, DataType dataType, int index, String nullFormat) {
    if (dataType == DataType.STRING) {
      String value = seriesData.getStringArray().getValues(index);
      return NullValues.STRING.equals(value) ? nullFormat : value;
    } else if (dataType == DataType.DOUBLE) {
      double value = seriesData.getDoubleArray().getValues(index);
      return Double.isNaN(value) ? nullFormat : value;
    } else if (dataType == DataType.BOOL) {
      return seriesData.getBoolArray().getValues(index);
    } else if (dataType == DataType.DURATION) {
      Duration value = seriesData.getDurationArray().getValues(index);
      return NullValues.DURATION.equals(value) ? nullFormat : value;
    } else if (dataType == DataType.FLOAT) {
      float value = seriesData.getFloatArray().getValues(index);
      return Float.isNaN(value) ? nullFormat : value;
    } else if (dataType == DataType.INT32) {
      int value = seriesData.getInt32Array().getValues(index);
      return NullValues.INT32 == value ? nullFormat : value;
    } else if (dataType == DataType.INT64) {
      long value = seriesData.getInt64Array().getValues(index);
      return NullValues.INT64 == value ? nullFormat : value;
    } else if (dataType == DataType.TIMESTAMP) {
      Timestamp value = seriesData.getTimestampArray().getValues(index);
      return NullValues.TIMESTAMP.equals(value) ? nullFormat : value;
    } else {
      throw new NotImplementedException(dataType + " is not implemented");
    }
  }
}

class TableData {
  List<Row> Rows = new ArrayList<Row>();
  
  public String toString() {
    return Rows.toString();
  }
}

class Row {
  List<String> Cells = new ArrayList<String>();

  public String toString() {
    return Cells.toString();
  }
}

 

import uuid
import math

import pandas as pd

from fds.protobuf.stach.Package_pb2 import Package
from fds.protobuf.stach.NullValues import NullValues
from fds.protobuf.stach.table.DataType_pb2 import DataType

class StachExtensions:
    """The purpose of this class is to provide the helper methods for converting Stach to Tabular format"""

    @staticmethod
    def generate_excel(package):
        for table in StachExtensions.convert_to_table_format(package):
            writer = pd.ExcelWriter(str(uuid.uuid1()) + ".xlsx")
            table.to_excel(excel_writer=writer)
            writer.save()
            writer.close()

    @staticmethod
    def convert_to_table_format(package):
        tables = list()
        for primary_table_id in package.primary_table_ids:
            tables.append(StachExtensions.generate_table(package, primary_table_id))
        return tables

    @staticmethod
    def generate_table(package_response, primary_table_id):

        if isinstance(package_response, Package):
            primary_table = package_response.tables[primary_table_id]
            header_id = primary_table.definition.header_table_id
            header_table = package_response.tables[header_id]
            dimension_columns = list(filter(lambda column_obj: column_obj.is_dimension, primary_table.definition.columns))
            dimension_columns_count = len(dimension_columns)
            row_count = len(primary_table.data.rows)
            header_row_count = len(header_table.data.rows)

            headers = list(list())
            # Constructs the column headers by considering dimension columns and header rows
            for series_definition_column in header_table.definition.columns:
                header_row = list()
                for i in range(0, dimension_columns_count, 1):
                    header_row.append(dimension_columns[i].description)

                for i in range(0, header_row_count, 1):
                    header_row.append(str(SeriesDataHelper.get_value_helper(header_table.data.columns[series_definition_column.id], series_definition_column.type, i, series_definition_column.format.null_format)))
                headers.append(header_row)

            data = list(list())
            # Constructs the column data
            for i in range(0, row_count, 1):
                data_row = list()
                for series_definition_column in primary_table.definition.columns:
                    data_row.append(str(SeriesDataHelper.get_value_helper(primary_table.data.columns[series_definition_column.id], series_definition_column.type, i, series_definition_column.format.null_format)))
                data.append(data_row)

            data_frame = pd.DataFrame(data=data)
            data_frame.columns = pd.MultiIndex.from_arrays(headers)
            return data_frame

        else:
            ValueError("Response data passed should be of package type.")


class SeriesDataHelper:

    @staticmethod
    def get_value_helper(series_data, datatype, index, null_format):
        if DataType.Name(datatype) == "STRING":
            return SeriesDataHelper.null_value_handler(datatype, series_data.string_array.values[index], null_format)
        elif DataType.Name(datatype) == "DOUBLE":
            return SeriesDataHelper.null_value_handler(datatype, series_data.double_array.values[index], null_format)
        elif DataType.Name(datatype) == "FLOAT":
            return SeriesDataHelper.null_value_handler(datatype, series_data.float_array.values[index], null_format)
        elif DataType.Name(datatype) == "INT32":
            return SeriesDataHelper.null_value_handler(datatype, series_data.int32_array.values[index], null_format)
        elif DataType.Name(datatype) == "INT64":
            return SeriesDataHelper.null_value_handler(datatype, series_data.int64_array.values[index], null_format)
        elif DataType.Name(datatype) == "BOOL":
            return SeriesDataHelper.null_value_handler(datatype, series_data.bool_array.values[index], null_format)
        elif DataType.Name(datatype) == "DURATION":
            return SeriesDataHelper.null_value_handler(datatype, series_data.duration_array.values[index], null_format)
        elif DataType.Name(datatype) == "TIMESTAMP":
            return SeriesDataHelper.null_value_handler(datatype, series_data.timestamp_array.values[index], null_format)
        else:
            ValueError("The datatype is not implemented")

    @staticmethod
    def null_value_handler(datatype, value, null_format):
        if DataType.Name(datatype) == "STRING":
            if NullValues.STRING == value:
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "DOUBLE":
            if math.isnan(value):
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "FLOAT":
            if math.isnan(value):
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "INT32":
            if NullValues.INT32 == value:
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "INT64":
            if NullValues.INT64 == value:
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "DURATION":
            if NullValues.DURATION.equals(value):
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "TIMESTAMP":
            if NullValues.TIMESTAMP.equals(value):
                return null_format
            else:
                return value
        else:
            return value

 

Building and running a calculation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using FactSet.AnalyticsAPI.PAEngineAPI.v1.Api;
using FactSet.AnalyticsAPI.PAEngineAPI.v1.Client;
using FactSet.AnalyticsAPI.PAEngineAPI.v1.Model;

namespace FactSet.AnalyticsAPI.PAEngineAPI.v1.Examples
{
    class Program
    {
        private static Configuration configuration;

        private static Configuration GetConfiguration()
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            if (configuration != null)
            {
                return configuration;
            }

            configuration = new Configuration
            {
                BasePath = "https://api.factset.com",
                Username = "<username-serial>",
                Password = "<apiKey>"
            };

            return configuration;
        }

        static void Main(string[] args)
        {
            var componentsApi = new ComponentsApi(GetConfiguration());

            // PA_DOCUMENTS:DEFAULT is default document available to all users.
            ApiResponse<Dictionary<string, ComponentListEntity>> componentGetAllResponse =
                componentsApi.GetAllWithHttpInfo("PA_DOCUMENTS:DEFAULT");
            var componentId = componentGetAllResponse.Data.First().Key;

            var account = "BENCH:SP50";
            var benchmark = "BENCH:R.1000";

            CalculationParameters inputBody = new CalculationParameters(componentId, account, benchmark);
            var calculationsApi = new CalculationsApi(GetConfiguration());
            var createrCalculationResponse = calculationsApi.CreateWithHttpInfo(inputBody);

            if (createrCalculationResponse.StatusCode != 202)
            {
                LogError(createrCalculationResponse);
                return;
            }

            // Parse the the Location header for calculation Id
            createrCalculationResponse.Headers.TryGetValue("Location", out string location);
            if (string.IsNullOrWhiteSpace(location))
            {
                LogError(createrCalculationResponse);
                return;
            }

            var splits = location.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            var calculationId = splits.Last();
            ApiResponse<object> getCalculationStatus = null;
            while (getCalculationStatus == null || getCalculationStatus.StatusCode == 202)
            {
                getCalculationStatus = calculationsApi.GetStatusByIdWithHttpInfo(calculationId);
                if (getCalculationStatus.StatusCode != 202)
                {
                    continue;
                }

                if (getCalculationStatus.Headers.ContainsKey("Cache-Control"))
                {
                    Console.WriteLine("Progress: " + getCalculationStatus.Headers["x-factset-api-pickup-progress"]);

                    var ageValue = getCalculationStatus.Headers["Cache-Control"];
                    if (string.IsNullOrWhiteSpace(ageValue))
                    {
                        Console.WriteLine("Sleeping for 2 seconds");
                        // Sleep for atleast 2 seconds.
                        Thread.Sleep(2000);
                    }
                    else
                    {
                        int age = int.Parse(ageValue.Replace("max-age=", ""));
                        Console.WriteLine($"Sleeping for {age} seconds");
                        Thread.Sleep(age * 1000);
                    }
                }
            }

            if (getCalculationStatus.StatusCode != 201)
            {
                LogError(getCalculationStatus);
                return;
            }

            var getCalculationResult = calculationsApi.GetResultByIdWithHttpInfo(calculationId);
            if (getCalculationResult.StatusCode != 200)
            {
                Console.WriteLine("Error!!!");
                Console.WriteLine("Status Code: " + getCalculationResult.StatusCode);
                Console.WriteLine("Request Key: " + getCalculationResult.Headers["X-DataDirect-Request-Key"]);
                return;
            }

            var package = getCalculationResult.Data;
            Console.WriteLine(package.ToString());
            Console.ReadKey();
        }

        private static void LogError(ApiResponse<object> response)
        {
            Console.WriteLine("Error!!!");
            Console.WriteLine("Status Code: " + response.StatusCode);
            Console.WriteLine("Request Key: " + response.Headers["X-DataDirect-Request-Key"]);
        }
    }
}

 

import java.util.List;
import java.util.Map;

import com.factset.protobuf.stach.PackageProto.Package;

import factset.analyticsapi.paengineapi.v1.ApiClient;
import factset.analyticsapi.paengineapi.v1.ApiException;
import factset.analyticsapi.paengineapi.v1.ApiResponse;
import factset.analyticsapi.paengineapi.v1.api.CalculationsApi;
import factset.analyticsapi.paengineapi.v1.api.ComponentsApi;
import factset.analyticsapi.paengineapi.v1.models.*;

public class Example {
  private static ApiClient apiClient = null;

  public static void main(String[] args) throws InterruptedException {
    try {
      ComponentsApi componentsApi = new ComponentsApi(apiClientFactory());
      Map<String, ComponentListEntity> components = componentsApi.getAll("PA_DOCUMENTS:DEFAULT");
      
      // Select required componentId from the above components list. In this example, we are using the first componentId.
      String componentId = components.entrySet().iterator().next().getKey();
      System.out.println("ComponentID: " + componentId);

      CalculationParameters parameters = new CalculationParameters();
      parameters.componentid(componentId);
      parameters.setAccount("BENCH:SP50");
      parameters.setBenchmark("BENCH:R.2000");

      CalculationsApi calculationsApi = new CalculationsApi(apiClientFactory());
      ApiResponse<Void> calculationResult = calculationsApi.createWithHttpInfo(parameters);

      Map<String, List<String>> headers = calculationResult.getHeaders();
      String locationToPoll = headers.get("Location").get(0);
      String[] splits = locationToPoll.split("/");
      String requestId = splits[splits.length - 1];

      ApiResponse<Void> calculationPollResponse = calculationsApi.getStatusByIdWithHttpInfo(requestId);

      while (calculationPollResponse.getStatusCode() == 202) {
        List<String> progress = calculationPollResponse.getHeaders().get("x-factset-api-pickup-progress");
        if (progress != null) {
          System.out.println("Progress: " + progress.get(0));
        }

        List<String> cacheControl = calculationPollResponse.getHeaders().get("cache-control");
        if (cacheControl != null) {
          int maxAge = Integer.parseInt(cacheControl.get(0).replaceAll("max-age=", ""));
          System.out.println("Sleeping for: " + maxAge + " seconds");
          Thread.sleep(maxAge * 1000);
        } else {
          System.out.println("Sleeping for: 2 seconds");
          Thread.sleep(2 * 1000);
        }

        calculationPollResponse = calculationsApi.getStatusByIdWithHttpInfo(requestId);
      }

      if (calculationPollResponse.getStatusCode() != 201) {
        System.err.println("Error!!!");
        System.err.println(
            "x-datadirect-request-key: " + calculationPollResponse.getHeaders().get("x-datadirect-request-key").get(0));
        return;
      }

      ApiResponse<Package> calculationResultResponse = calculationsApi.getResultByIdWithHttpInfo(requestId);
      // System.out.println(calculationResultResponse.getData());
      List<TableData> tables = StachExtensions.convertToTableFormat(calculationResultResponse.getData()); // To convert result to 2D tables.
      System.out.println(tables.get(0)); // Prints the result in 2D table format.
      // StachExtensions.generateExcel(calculationResultResponse.getData()); // To get the result in table format exported to excel file.

    } catch (ApiException e) {
      System.err.println("ResponseBody: " + e.getResponseBody());
      System.err.println("x-datadirect-request-key: " + e.getResponseHeaders().get("x-datadirect-request-key").get(0));
      e.printStackTrace();
    }
  }

  private static ApiClient apiClientFactory() {
    if (apiClient != null) {
      return apiClient;
    }

    apiClient = new ApiClient();
    apiClient.setBasePath("https://api.factset.com");
    apiClient.setUsername("username-serial"); // username-serial combination
    apiClient.setPassword("apiKey"); // api key

    apiClient.setVerifyingSsl(false);

    return apiClient;
  }
}

 

import time

from google.protobuf.json_format import MessageToJson
from google.protobuf.json_format import MessageToDict

from stach_extensions import StachExtensions
from fds.analyticsapi.paengineapi.v1.api_client import ApiClient
from fds.analyticsapi.paengineapi.v1.configuration import Configuration
from fds.analyticsapi.paengineapi.v1.api.components_api import ComponentsApi
from fds.analyticsapi.paengineapi.v1.api.calculations_api import CalculationsApi
from fds.analyticsapi.paengineapi.v1.models.calculation_parameters import CalculationParameters

config = Configuration()
config.host = "https://api.factset.com"
config.username = "username-serial" # username-serial combination
config.password = "apiKey" # api key
config.verify_ssl = False

api_client = ApiClient(config)

componentsApi = ComponentsApi(api_client)
component_api_get_result = componentsApi.get_all_with_http_info(documentname="PA_DOCUMENTS:DEFAULT")

# Select required componentId from the above components list. In this example, we are using the first componentId.
componentId = list(component_api_get_result[0].keys())[0]
print("Component ID: " + componentId)

calculation_params = CalculationParameters(componentId, "BENCH:SP50", "BENCH:R.2000")
calculationsApi = CalculationsApi(api_client)
calculation_api_post_result =  calculationsApi.create_with_http_info(parameters=calculation_params)

if(calculation_api_post_result[1] != 202):
    print("x-datadirect-request-key: " + calculation_api_post_result[2].get('x-datadirect-request-key'))
    quit()

statusLocation = calculation_api_post_result[2].get('location')
splits = statusLocation.split('/')
calculationId = splits[-1]
print("Calculation Id: " + calculationId)

calculation_status_result = calculationsApi.get_status_by_id_with_http_info(calculationId)
while calculation_status_result[1] == 202:
    progress = calculation_status_result[2].get('x-factset-api-pickup-progress')
    if(progress != None):
        print("Progress: " + progress)
    ageValue = calculation_status_result[2].get('cache-control')
    if(ageValue != None):
        maxAge = ageValue.replace("max-age=", "")
        print("Sleeping for: " + maxAge)
        time.sleep(int(maxAge))
    calculation_status_result = calculationsApi.get_status_by_id_with_http_info(calculationId)

if(calculation_status_result[1] != 201):
    print("x-datadirect-request-key: " + calculation_api_post_result[2].get('x-datadirect-request-key'))
    quit()

calculation_api_result = calculationsApi.get_result_by_id_with_http_info(calculationId)
# print(calculation_api_result[0])

if(calculation_api_result[1] != 200):
    print("x-datadirect-request-key: " + calculation_api_result[2].get('x-datadirect-request-key'))
    quit()

# print(MessageToJson(calculation_api_result[0])) # To print the result object as a JSON.
# print(MessageToDict(calculation_api_result[0])) # To print the result object as a Dictionary.
tables = StachExtensions.convert_to_table_format(calculation_api_result[0]) # To convert result to 2D tables.
print(tables[0]) # Prints the result in 2D table format.
# StachExtensions.generate_excel(calculation_api_result[0]) # To get the result in table format exported to excel file.

 

Converting API output to Table format
using System;
using System.Collections.Generic;
using System.Linq;
using FactSet.Protobuf.Stach;
using FactSet.Protobuf.Stach.Table;

namespace FactSet.AnalyticsAPI.PAEngineAPI.v1.Examples
{
    public static class StachExtensions
    {
        public static void GenerateCSV(this Package package)
        {
            foreach (var table in package.ConvertToTableFormat())
            {
                System.IO.File.WriteAllText($"{Guid.NewGuid():N}.csv", table.ToString());
            }
        }

        public static List<Table> ConvertToTableFormat(this Package package)
        {
            var tables = new List<Table>();
            foreach (var primaryTableId in package.PrimaryTableIds)
            {
                tables.Add(GenerateTable(package, primaryTableId));
            }

            return tables;
        }

        private static Table GenerateTable(Package package, string primaryTableId)
        {
            var primaryTable = package.Tables[primaryTableId];
            var headerId = primaryTable.Definition.HeaderTableId;
            var headerTable = package.Tables[headerId];
            var columnIds = primaryTable.Definition.Columns.Select(c => c.Id).ToList();
            var headerColumnIds = headerTable.Definition.Columns.Select(c => c.Id).ToList();
            var dimensionColumnsCount = primaryTable.Definition.Columns.Count(c => c.IsDimension);
            var rowCount = primaryTable.Data.Rows.Count;
            var headerRowCount = headerTable.Data.Rows.Count;

            var table = new Table
            {
                Rows = new List<Row>()
            };
            // Constructs the column headers by considering dimension columns and header rows
            foreach (var columnId in headerColumnIds)
            {
                var headerRow = new Row { Cells = new List<string>() };
                for (int j = 0; j < dimensionColumnsCount; j++)
                {
                    headerRow.Cells.Add("");
                }

                for (int i = 0; i < headerRowCount; i++)
                {
                    headerRow.Cells.Add(Convert.ToString(headerTable.Data.Columns[columnId]
                        .GetValueHelper(headerTable.Definition.Columns.First(c => c.Id == columnId).Type, i)));
                }
                table.Rows.Add(headerRow);
            }
            // Constructs the column data
            for (int i = 0; i < rowCount; i++)
            {
                var dataRow = new Row { Cells = new List<string>() };
                foreach (var columnId in columnIds)
                {
                    dataRow.Cells.Add(Convert.ToString(primaryTable.Data.Columns[columnId]
                        .GetValueHelper(primaryTable.Definition.Columns.First(c => c.Id == columnId).Type, i)));
                }
                table.Rows.Add(dataRow);
            }

            return table;
        }
    }

    public static class SeriesDataHelper
    {
        public static object GetValueHelper(this SeriesData seriesData, DataType dataType, int index)
        {
            switch (dataType)
            {
                case DataType.Bool:
                {
                    return seriesData.BoolArray?.Values?[index];
                }
                case DataType.Double:
                {
                    return seriesData.DoubleArray?.Values?[index];
                }
                case DataType.Duration:
                {
                    var v = seriesData.DurationArray?.Values?[index];
                    return v?.ToTimeSpan();
                }
                case DataType.Float:
                {
                    return seriesData.FloatArray?.Values?[index];
                }
                case DataType.Int32:
                {
                    return seriesData.Int32Array?.Values?[index];
                }
                case DataType.Int64:
                {
                    return seriesData.Int64Array?.Values?[index];
                }
                case DataType.String:
                {
                    return seriesData.StringArray?.Values?[index];
                }
                case DataType.Timestamp:
                {
                    var v = seriesData.TimestampArray?.Values?[index];
                    return v?.ToDateTime();
                }
                default:
                    throw new NotImplementedException($"{dataType} is not implemented");
            }
        }
    }

    public class Table
    {
        public List<Row> Rows { get; set; }

        public override string ToString()
        {
            return string.Join(Environment.NewLine, Rows);
        }
    }

    public class Row
    {
        public List<string> Cells { get; set; }

        public override string ToString()
        {
            return string.Join(",", Cells.Select(c => c.Replace(",", "")));
        }
    }
}

 

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.poi.ss.formula.eval.NotImplementedException;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.factset.protobuf.stach.NullValues;
import com.factset.protobuf.stach.PackageProto.Package;
import com.factset.protobuf.stach.table.DataTypeProto.DataType;
import com.factset.protobuf.stach.table.SeriesDataProto.SeriesData;
import com.factset.protobuf.stach.table.SeriesDefinitionProto.SeriesDefinition;
import com.factset.protobuf.stach.table.TableProto.Table;

import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;

public class StachExtensions {
  // The purpose of this class is to provide the helper methods for converting stach to Tabular format.

  public static void generateExcel(Package packageObj) {
    for (TableData table : convertToTableFormat(packageObj)) {
      writeDataToExcel(table, UUID.randomUUID().toString() + ".xlsx");
    }
  }

  public static List<TableData> convertToTableFormat(Package packageObj) {
    List<TableData> tables = new ArrayList<TableData>();
    for (String primaryTableId : packageObj.getPrimaryTableIdsList()) {
      tables.add(generateTable(packageObj, primaryTableId));
    }
    return tables;
  }

  private static TableData generateTable(Package packageObj, String primaryTableId) {
    Map<String, Table> tablesMap = packageObj.getTablesMap();
    Table primaryTable = tablesMap.get(primaryTableId);
    String headerId = primaryTable.getDefinition().getHeaderTableId();
    Table headerTable = tablesMap.get(headerId);
    int headerRowCount = headerTable.getData().getRowsCount();
    int rowsCount = primaryTable.getData().getRowsCount();

    TableData table = new TableData();

    // Construct the column headers by considering dimension columns and header rows.
    List<SeriesDefinition> headerTableSeriesDefinitions = headerTable.getDefinition().getColumnsList();
    List<SeriesDefinition> primaryTableSeriesDefinitions = primaryTable.getDefinition().getColumnsList();

    Map<String, SeriesData> headerTableColumns = headerTable.getData().getColumnsMap();
    Map<String, SeriesData> primaryTableColumns = primaryTable.getData().getColumnsMap();

    for (SeriesDefinition headerTableseriesDefinition : headerTableSeriesDefinitions) {
      Row headerRow = new Row();
      for (SeriesDefinition primaryTableSeriesDefinition : primaryTableSeriesDefinitions) {
        if (primaryTableSeriesDefinition.getIsDimension()) {
          headerRow.Cells.add(primaryTableSeriesDefinition.getDescription());
        }
      }

      String headerColumnId = headerTableseriesDefinition.getId();
      String nullFormat = headerTableseriesDefinition.getFormat().getNullFormat();
      for (int i = 0; i < headerRowCount; i++) {
        headerRow.Cells.add(SeriesDataHelper.getValueHelper(headerTableColumns.get(headerColumnId),
            headerTableseriesDefinition.getType(), i, nullFormat).toString());
      }
      table.Rows.add(headerRow);
    }

    // Construct the column data
    for (int i = 0; i < rowsCount; i++) {
      Row dataRow = new Row();
      for (SeriesDefinition primaryTableSeriesDefinition : primaryTableSeriesDefinitions) {
        String nullFormat = primaryTableSeriesDefinition.getFormat().getNullFormat();
        String primaryTableColumnId = primaryTableSeriesDefinition.getId();
        dataRow.Cells.add(SeriesDataHelper.getValueHelper(primaryTableColumns.get(primaryTableColumnId),
            primaryTableSeriesDefinition.getType(), i, nullFormat).toString());
      }
      table.Rows.add(dataRow);
    }
    return table;
  }

  private static void writeDataToExcel(TableData table, String fileLocation) {
    XSSFWorkbook workbook = new XSSFWorkbook();
    XSSFSheet sheet = workbook.createSheet("PA Report");

    int rowsSize = table.Rows.size();
    for (int rowIndex = 0; rowIndex < rowsSize; rowIndex++) {
      XSSFRow xsswRow = sheet.createRow(rowIndex);
      List<String> cells = table.Rows.get(rowIndex).Cells;
      for (int cellIndex = 0; cellIndex < cells.size(); cellIndex++) {
        XSSFCell xssfCell = xsswRow.createCell(cellIndex);
        xssfCell.setCellValue(cells.get(cellIndex));
      }
    }

    try {
      FileOutputStream out = new FileOutputStream(new File(fileLocation));
      workbook.write(out);
      out.close();
      workbook.close();
    } catch (Exception e) {
      System.err.println("Failed to write data to excel");
      e.printStackTrace();
    }
  }
}

class SeriesDataHelper {
  public static Object getValueHelper(SeriesData seriesData, DataType dataType, int index, String nullFormat) {
    if (dataType == DataType.STRING) {
      String value = seriesData.getStringArray().getValues(index);
      return NullValues.STRING.equals(value) ? nullFormat : value;
    } else if (dataType == DataType.DOUBLE) {
      double value = seriesData.getDoubleArray().getValues(index);
      return Double.isNaN(value) ? nullFormat : value;
    } else if (dataType == DataType.BOOL) {
      return seriesData.getBoolArray().getValues(index);
    } else if (dataType == DataType.DURATION) {
      Duration value = seriesData.getDurationArray().getValues(index);
      return NullValues.DURATION.equals(value) ? nullFormat : value;
    } else if (dataType == DataType.FLOAT) {
      float value = seriesData.getFloatArray().getValues(index);
      return Float.isNaN(value) ? nullFormat : value;
    } else if (dataType == DataType.INT32) {
      int value = seriesData.getInt32Array().getValues(index);
      return NullValues.INT32 == value ? nullFormat : value;
    } else if (dataType == DataType.INT64) {
      long value = seriesData.getInt64Array().getValues(index);
      return NullValues.INT64 == value ? nullFormat : value;
    } else if (dataType == DataType.TIMESTAMP) {
      Timestamp value = seriesData.getTimestampArray().getValues(index);
      return NullValues.TIMESTAMP.equals(value) ? nullFormat : value;
    } else {
      throw new NotImplementedException(dataType + " is not implemented");
    }
  }
}

class TableData {
  List<Row> Rows = new ArrayList<Row>();
  
  public String toString() {
    return Rows.toString();
  }
}

class Row {
  List<String> Cells = new ArrayList<String>();

  public String toString() {
    return Cells.toString();
  }
}

 

import uuid
import math

import pandas as pd

from fds.protobuf.stach.Package_pb2 import Package
from fds.protobuf.stach.NullValues import NullValues
from fds.protobuf.stach.table.DataType_pb2 import DataType

class StachExtensions:
    """The purpose of this class is to provide the helper methods for converting Stach to Tabular format"""

    @staticmethod
    def generate_excel(package):
        for table in StachExtensions.convert_to_table_format(package):
            writer = pd.ExcelWriter(str(uuid.uuid1()) + ".xlsx")
            table.to_excel(excel_writer=writer)
            writer.save()
            writer.close()

    @staticmethod
    def convert_to_table_format(package):
        tables = list()
        for primary_table_id in package.primary_table_ids:
            tables.append(StachExtensions.generate_table(package, primary_table_id))
        return tables

    @staticmethod
    def generate_table(package_response, primary_table_id):

        if isinstance(package_response, Package):
            primary_table = package_response.tables[primary_table_id]
            header_id = primary_table.definition.header_table_id
            header_table = package_response.tables[header_id]
            dimension_columns = list(filter(lambda column_obj: column_obj.is_dimension, primary_table.definition.columns))
            dimension_columns_count = len(dimension_columns)
            row_count = len(primary_table.data.rows)
            header_row_count = len(header_table.data.rows)

            headers = list(list())
            # Constructs the column headers by considering dimension columns and header rows
            for series_definition_column in header_table.definition.columns:
                header_row = list()
                for i in range(0, dimension_columns_count, 1):
                    header_row.append(dimension_columns[i].description)

                for i in range(0, header_row_count, 1):
                    header_row.append(str(SeriesDataHelper.get_value_helper(header_table.data.columns[series_definition_column.id], series_definition_column.type, i, series_definition_column.format.null_format)))
                headers.append(header_row)

            data = list(list())
            # Constructs the column data
            for i in range(0, row_count, 1):
                data_row = list()
                for series_definition_column in primary_table.definition.columns:
                    data_row.append(str(SeriesDataHelper.get_value_helper(primary_table.data.columns[series_definition_column.id], series_definition_column.type, i, series_definition_column.format.null_format)))
                data.append(data_row)

            data_frame = pd.DataFrame(data=data)
            data_frame.columns = pd.MultiIndex.from_arrays(headers)
            return data_frame

        else:
            ValueError("Response data passed should be of package type.")


class SeriesDataHelper:

    @staticmethod
    def get_value_helper(series_data, datatype, index, null_format):
        if DataType.Name(datatype) == "STRING":
            return SeriesDataHelper.null_value_handler(datatype, series_data.string_array.values[index], null_format)
        elif DataType.Name(datatype) == "DOUBLE":
            return SeriesDataHelper.null_value_handler(datatype, series_data.double_array.values[index], null_format)
        elif DataType.Name(datatype) == "FLOAT":
            return SeriesDataHelper.null_value_handler(datatype, series_data.float_array.values[index], null_format)
        elif DataType.Name(datatype) == "INT32":
            return SeriesDataHelper.null_value_handler(datatype, series_data.int32_array.values[index], null_format)
        elif DataType.Name(datatype) == "INT64":
            return SeriesDataHelper.null_value_handler(datatype, series_data.int64_array.values[index], null_format)
        elif DataType.Name(datatype) == "BOOL":
            return SeriesDataHelper.null_value_handler(datatype, series_data.bool_array.values[index], null_format)
        elif DataType.Name(datatype) == "DURATION":
            return SeriesDataHelper.null_value_handler(datatype, series_data.duration_array.values[index], null_format)
        elif DataType.Name(datatype) == "TIMESTAMP":
            return SeriesDataHelper.null_value_handler(datatype, series_data.timestamp_array.values[index], null_format)
        else:
            ValueError("The datatype is not implemented")

    @staticmethod
    def null_value_handler(datatype, value, null_format):
        if DataType.Name(datatype) == "STRING":
            if NullValues.STRING == value:
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "DOUBLE":
            if math.isnan(value):
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "FLOAT":
            if math.isnan(value):
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "INT32":
            if NullValues.INT32 == value:
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "INT64":
            if NullValues.INT64 == value:
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "DURATION":
            if NullValues.DURATION.equals(value):
                return null_format
            else:
                return value
        elif DataType.Name(datatype) == "TIMESTAMP":
            if NullValues.TIMESTAMP.equals(value):
                return null_format
            else:
                return value
        else:
            return value

 

Change Logs

Change Logs
v2
Summary
  • v2.0.0 - Released on 08/15/19 (Combined PA Engine API v1 and PA Batch API v1 into PA Engine API v2)
Functionality Additions
  • Initial release
Changes
  • No changes
Bug Fixes
  • No changes
v1 (Retiring on 10/31/19)
Summary
  • v1.7.3 - Released on 08/15/19
  • v1.7.2 - Released on 07/16/19
  • v1.7.1 - Released on 06/13/19
  • v1.7.0 - Released on 04/22/19
  • v1.6.1 - Released on 04/10/19
  • v1.6.0 - Released on 03/27/19
  • v1.5.0 - Released on 02/28/19
  • v1.4.0 - Released on 01/29/19
  • v1.3.0 - Released on 11/29/18
  • v1.2.0 - Released on 10/19/18
  • v1.1.0 - Released on 10/12/18
  • v1.0.0 - Released on 08/30/18
Functionality Additions
  • Retired Vault endpoints in favor of Vault API [v1.7.3]
  • Added incoming rate limit rules for user-serials on trial [v1.7.1]
  • Added account listing endpoint to list account files, composite files and sub-directories in a given directory [v1.7.0]
  • Added document listing endpoint to list PA3 documents in a given directory [v1.7.0]
  • Increased the maximum column count within a PA component to be computed per calculation from 25 to 40 [v1.5.1]
  • Added the option to override columns in a PA component through Calculations API’s Run Calculation endpoint [v1.5.0]
  • Added an endpoint to list all available FactSet-defined and client-defined columns [v1.5.0]
  • Added an endpoint to list all available column statistics that can be applied to a column when overriding in a PA Component through Calculations API’s Run Calculation endpoint [v1.5.0]
  • Returning non-single date frequency components in the components lookup endpoints [v1.4.0]
  • Added additional validations for account and benchmark in the create calculation endpoint [v1.4.0]
  • Added Get All Outstanding Calculations endpoint to fetch progress of all previously requested calculations [v1.3.0]
  • Added Rate Limit response headers to Run Calculation and Run Vault Calculation endpoints [v1.3.0]
  • Added support for Accept request header to all HTTP GET endpoints [v1.3.0]
  • Added API Keys as an additional authentication methodology. One-Time Password (OTP) authentication will be phased out eventually [v1.2.0]
  • [Alpha Release] Added Calculation API for Vault data [v1.1.0]
  • [Alpha Release] Added Helper APIs to lookup Vault configuration(s) by user-serial and by Vault identifier [v1.1.0]
  • Added the option to override groups in a PA component through Calculations API’s Run Calculation endpoint [v1.0.0]
  • Added Helper API for groups lookup [v1.0.0]
  • Added error reasons in response body for responses greater than or equal to HTTP status code 400 [v1.0.0]
  • Added API endpoint to fetch a component’s calculation settings [v1.0.0]
  • Added Api-Supported-Versions response header to describe all API versions supported by a given endpoint [v1.0.0]
Changes
  • Hostname for all endpoints has been changed from “ondemand.factset.com” to “api.factset.com” [v1.0.0]
  • Get Calculation Result Endpoint has been broken down into Get Calculation Status and Get Calculation Result endpoints [v1.0.0]
  • Run Calculation endpoint’s “documentname” property has been retired. Component identifiers will need to be fetched from ‘Fetch Component Settings’ endpoint [v1.0.0]
Bug Fixes
  • Fixed rate limit timeout from 30 milliseconds to 30 seconds [v1.7.2]
  • Fixed error reason when providing incorrect version [v1.3.0]
  • Non-successful responses (HTTP status codes >= 300) will no longer fill up a user-serial’s quota [v1.2.0]