Skip to content
Connect > Client Libraries

Client Libraries Quickstart

Connect a client, run a query, and watch a live subscription, in Python and in C++. It takes a few minutes and assumes a server with a Center reachable, and a table to read (this example uses one called QUOTES).

The port numbers below are the common convention. Only the JDBC port (3280) is a real default: set the Center port (shown as 3270) and Relay port (3289) to whatever your deployment uses. See Configuration → Ports.

1. Connect

You create one client, describe a cluster with the configs your program needs, and add it.

1
2
3
4
5
6
7
8
import pyami.rt.ami_client as ac

node = ac.AmiCluster("N1", "localhost")
node.jdbc_config   = ac.AmiDatabaseConfig("3280", True, "demo", "demo123", 60000, 10000)
node.center_config = ac.AmiSubscriptionConfig("3270", True)

client = ac.AmiClient()
client.add_cluster(node, sync=True)     # sync=True blocks until connected
1
2
3
4
5
6
7
8
9
#include "ami_client.hpp"
using namespace ami;

Cluster node("N1", "localhost");
node.database_config()     = DatabaseConfig("3280", /*ssl=*/true, {}, "demo", "demo123");
node.subscription_config() = SubscriptionConfig("3270", /*ssl=*/true);

Client client;
client.add_cluster(node, /*sync=*/true);   // blocks until connected

The third argument to the C++ DatabaseConfig ({}) is the failover-endpoints list; leave it empty for a single server.

2. Run a query

A JDBC query returns a table. In Python it comes back as a column-oriented dict you can hand straight to pandas; in C++ it comes back as a TableAccessor you iterate.

1
2
3
4
5
result = client.sql("N1", 'SELECT SYM, bid, ask FROM QUOTES WHERE SYM == "IBM"')
# result == {"SYM": ["IBM"], "bid": array([123.4]), "ask": array([123.5])}   # strings are lists, numeric columns are numpy arrays

import pandas as pd
df = pd.DataFrame(result)

The equality operator is == and string literals use double quotes, this is the expression syntax, not ANSI SQL. On error the dict has an "error" key; an INSERT/UPDATE returns {"rows_affected": n}.

1
2
3
4
5
6
7
8
9
auto result = client.sql("N1", "SELECT SYM, bid, ask FROM QUOTES");
if (result && result->has_result_set()) {
    const auto & cols = result->columns();
    while (result->cursor_advance()) {
        auto row = result->cursor_row_number();
        // read cols[c] at row
    }
    result->print(std::cout);
}

Results are capped at 10 000 rows by default

The default row limit is 10 000. A larger result is truncated; raise the limit on the JDBC config or narrow the query. See Configuration.

3. Watch a table live

Attach a listener and subscribe. The client calls on_update for every row change, an add for each initial row, then update/delete as the table changes.

class MyListener:
    def on_subscribe(self, client, name, tables):
        print("subscribed to", tables)

    def on_update(self, client, name, update):
        # update.action: 1 = add, 2 = update, 3 = delete
        fields = dict(zip(update.field_names, update.row_data))
        print(update.action, update.table_name, update.id, fields)   # merge on update.id

client.add_client_listener(MyListener())
client.subscribe("N1", ["QUOTES"])
class MyListener : public ClientListener {
public:
    void on_subscribe(Client &, std::string_view name,
                      const std::vector<std::string> & tables) override {
        std::cout << "[SUBSCRIBED] " << name << "\n";
    }
    void on_update(Client &, std::string_view name,
                   const CenterIncrementalUpdate & u) override {
        // u.action_: 1 = add, 2 = update, 3 = delete
        std::cout << u.action_ << " " << u.table_name_ << " " << u.id_ << "\n";  // merge on id_
    }
};

client.listener_add(std::make_shared<MyListener>());
client.subscribe("N1", {"QUOTES"}, /*sync=*/true);

An update carries only the columns that changed. Merge it onto the row you already hold, keyed by the numeric id (not object_id, which is empty on subscription updates); do not replace the whole row or you blank the untouched fields. See Concepts → Merge on id.

Callbacks run on the client's threads

on_update runs on the client's own I/O thread. Copy anything you keep (the field data is valid only during the callback), keep the callback fast and run blocking work (and any same-connection call) on your own thread rather than in the callback, and guard shared state. See Concepts.

Where to go next

Verified live via the Python client (build 32437)

The Python round trip here, connect over mutual TLS, query, and subscribe, was reproduced against a live instance. The C++ snippets were not separately compiled and run.