Using HQ API

“HQ” is the business logic layer of the Q2 platform. It handles authentication, session, communication with cores, and database access. Easy access to HQ is one of the most powerful features of the Caliper SDK.

For our transaction history, we need a list of the recent transactions made against a selected account. We can easily get this using our HQ API. More specifically, we’re going to use the GetAccountHistoryById module module.

First, we’re going to import the API endpoint we wish to use at the top of the file with the other imports:

from q2_sdk.hq.hq_api.q2_api import GetAccountHistoryById

An HQ call using this endpoint resembles the following code sample (We’ll tell you where to add it below):

params_obj = GetAccountHistoryById.ParamsObj(
    self.logger,
    self.form_fields['account_id'],
    '',
    hq_credentials=self.hq_credentials
)

hq_response = await GetAccountHistoryById.execute(params_obj)

We need to construct a ParamsObj for our HQ call. Each endpoint has its own required parameters, information about which lives inside the code as type hints and parameter docstrings. A full reference guide to HQ API can be found here.

In our case, the ParamsObj specific to GetAccountHistoryById requires, in order:

  • Our built-in logger object

  • The account_id from the input, helpfully stored inside the self.form_fields dictionary

  • A filter, which we don’t need right now, so we’ll just set it to blank

  • self.hq_credentials is optional but highly recommended to allow flexible deployment options once in our datacenter

Before we update our AccountDashboard/extension.py, let’s take a minute to setup our results display. We’ll review this in detail in the next step. For now, create a file in AccountDashboard/templates called transaction_history.html.jinja2, and drop in the following:

<div>
    <a href="#default">Back to Accounts</a>
</div>

<div class="table-wrapper">
    <table>
    <thead>
        <tr>
        <th width="100">
            Date
        </th>
        <th>
            Description
        </th>
        <th class="text-right">
            Amount
        </th>
        </tr>
    </thead>
    <tbody>
        {% for transaction in transactions %}
            <tr>
                <td>{{transaction.display_date}}</td>
                <td>{{transaction.display_description}}</td>
                {% if transaction.is_credit %}
                    <td class="clr(const-stoplight-success)">+{{transaction.display_amount}}</td>
                {% else %}
                    <td>-{{transaction.display_amount}}</td>
                {% endif %}
            </tr>
        {% endfor %}
    </tbody>
    </table>
</div>

Now, for submit:

async def submit(self):
    params_obj = GetAccountHistoryById.ParamsObj(
        self.logger,
        self.form_fields['account_id'],
        '',
        hq_credentials=self.hq_credentials
    )

    hq_response = await GetAccountHistoryById.execute(params_obj)

    transaction_models = []

    for transaction in hq_response.result_node.Data.AllHostTransactions.Transactions:
        display_date = str(transaction.PostDate)
        display_amount = str(transaction.TxnAmount)
        is_credit = (transaction.SignedTxnAmount > 0)
        display_description = str(transaction.Description)

        transaction_models.append({
            'display_date': display_date,
            'display_amount': display_amount,
            'is_credit': is_credit,
            'display_description': display_description
        })

    template = self.get_template(
        'transaction_history.html.jinja2', {
            'header': "Transaction History",
            'transactions': transaction_models,
            'current_account_id': self.form_fields['account_id'],
        }
    )

    html = self.get_tecton_form(
        "Transaction History",
        custom_template=template,
        hide_submit_button=True
    )

    return html

Let’s make our actual HQ call. GetAccountHistoryById.execute is an asynchronous function, allowing our extensions to serve many hundreds of simultaneous user interactions. To take advantage of this, we use the await keyword provided with Python 3.5+. This is also why we define submit with the async helper. If you’ve ever dealt with asynchronous coding challenges, you’ll appreciate how easy modern Python makes this!

The data we wanted is nested pretty deeply inside our hq_response object: this structure is an objectified representation of the complex XML structure that HQ returns. Luckily, if you are using a good IDE like PyCharm, you should have type hinting for the most common response types to help you find what you need.

In this case, Transactions is the level we need. We iterate over that list, then convert and print the date and amount of each transaction made against this account, one per line. Try it out and see for yourself.

What we have now in the most basic kind of Caliper SDK extension: it renders a form, and handles the submission of that form.

In the next section, we’ll review the use of Templates and the role they play in the SDK.