fuxingloh / airtable

A lightweight Java 8 Airtable API client for https://airtable.com/api with all features implemented.
Apache License 2.0
19 stars 2 forks source link
airtable airtable-api java java-api

Airtable Java API Interface

This library support all features available in https://airtable.com/api.

CI codecov maven-central

Features:

Download

Hosted in Maven Central.

Maven

<dependency>
  <groupId>dev.fuxing</groupId>
  <artifactId>airtable-api</artifactId>
  <version>0.3.1</version>
</dependency>

Gradle

compile group: 'dev.fuxing', name: 'airtable-api', version: '0.3.2'

Example

Getting the AirtableTable interface.

AirtableApi api = new AirtableApi("key...");
AirtableTable table = api.base("app...").table("Table Name");

List

Querying and getting a list.

// List with offset support
AirtableTable.PaginationList list = table.list(querySpec -> {
    querySpec.view("View Name");
    querySpec.filterByFormula(LogicalOperator.EQ, field("Value"), value(1));
});

// For next pagination
list.getOffset();

Iterator

Iterator with automated build in support for pagination.

table.iterator().forEachRemaining(record -> {
    List<AttachmentField> images = record.getFieldAttachmentList("Images");
    String name = record.getFieldString("Name");
});

Query Spec Builder

All list querystring is supported with functional fluent formula builder.

List<AirtableRecord> list = table.list(query -> {
    // Localisation
    query.cellFormat("string")
    query.timeZone("Asia/Singapore");
    query.userLocale("af");

    // Data filtering
    query.view("View Name");
    query.fields("a", "b");

    // Sorting
    query.sort("field-name");
    query.sort("field-name", "desc");

    // Pagingation
    query.pageSize(50);
    query.maxRecords(1000);
    query.offset("rec...");

    // Vanilla String Formula: NOT({F} = '')
    query.filterByFormula("NOT({F} = '')");

    // Compile time Typesafe Query Formula
    // {Value}=1
    query.filterByFormula(LogicalOperator.EQ, AirtableFormula.Object.field("Value"), value(1));

    // 1+2
    query.filterByFormula(NumericOperator.ADD, AirtableFormula.Object.value(1), AirtableFormula.Object.value(2))

    // {f1}=(AND(1,{f2}))
    query.filterByFormula(LogicalOperator.EQ, field("f1"), parentheses(LogicalFunction.AND, value(1), field("f2")));
});

Getting an existing record

AirtableRecord record = table.get("rec...");

Creating a new record

AirtableRecord record = new AirtableRecord();
record.putField("Name", "Posted");

// Attachment support
CollaboratorField collaborator = new CollaboratorField();
collaborator.setEmail("me@email.com");
record.putField("Collaborator", collaborator);

AttachmentField attachment = new AttachmentField();
attachment.setUrl("https://upload.wikimedia.org/wikipedia/commons/5/56/Wiki_Eagle_Public_Domain.png");
record.putFieldAttachments("Attachments", Collections.singletonList(field));

record = table.post(record);

Patching an existing record

AirtableRecord record = new AirtableRecord();
record.setId("rec...");
record.putField("Name", "Patched");

record = table.patch(record);

Replacing an existing record

AirtableRecord record = new AirtableRecord();
record.setId("rec...");
record.putField("Name", "Replaced Entirely");

record = table.put(record);

Deleting an existing record

table.delete("rec...");

429 Auto Retry

Auto retry is enabled by default. To disable it, you can create an Executor without retry.

Executor executor = AirtableExecutor.newInstance(false);
AirtableApi api = new AirtableApi("key...", executor);
AirtableTable table = api.base("app...").table("Table Name");

Cache Module

Use Airtable as your main database with heavy caching strategy.

For many read heavy applicaiton, status 429; too many request can be problematic when developing for speed. Cache is a read-only interface that will ignore ignorable AirtableApiException (429, 500, 502, 503).

Creating an airtable cache.

AirtableCache uses a different HTTPClient with more concurent connection pool. RetryStrategy is also ignored.

AirtableCache cache = AirtableCache.create(builder -> builder
        .apiKey(System.getenv("AIRTABLE_API_KEY"))
        .app("app3h0gjxLX3Jomw8")
        .table("Test Table")
        // Optional cache control
        .withGet(maxRecords, cacheDuration, cacheTimeUnit)
        .withQuery(maxRecords, cacheDuration, cacheTimeUnit)
);

Get record by id

Get will always attempt to get the latest record from airtable server.
Fallback read from cache will only happen if any of the ignorable exception is thrown.

AirtableRecord record = cache.get("rec0W9eGVAFSy9Chb");

Get records results by query spec

Query will always attempt to get the latest result from airtable server.
Fallback read from cache will only happen if any of the ignorable exception is thrown.
The cache key used will be the querystring.

List<AirtableRecord> results = cache.query(querySpec -> {
    querySpec.filterByFormula(LogicalOperator.EQ, field("Name"), value("Name 1"));
});

Gradle Dependencies

compile group: 'dev.fuxing', name: 'airtable-api', version: '0.3.2'
compile group: 'dev.fuxing', name: 'airtable-cache', version: '0.3.2'

Mirror Module

Use Airtable as your stateless database view for EDA.

For many applications:

Implementation

// Example Database
Database database = new Database();

AirtableMirror mirror = new AirtableMirror(table, field("PrimaryKey in Airtable")) {
    @Override
    protected Iterator<AirtableRecord> iterator() {
        // Provide an iterator of all your records to mirror over to Airtable.
        Iterator<Database.Data> iterator = database.iterator();

        return new Iterator<AirtableRecord>() {
            @Override
            public boolean hasNext() {
                return iterator.hasNext();
            }

            @Override
            public AirtableRecord next() {
                Database.Data data = iterator.next();

                // Map into AirtableRecord
                AirtableRecord record = new AirtableRecord();
                record.putField("Name", data.name);
                record.putField("Checkbox", data.checkbox);
                ...
                return record;
            }
        };
    }

    // Whether a record from your (the iterator) is still same from airtable.
    protected boolean same(AirtableRecord fromIterator, AirtableRecord fromAirtable) {
        // You might want to add a timestamp to simplify checking
        String left = fromIterator.getFieldString("Name");
        String right = fromAirtable.getFieldString("Name");
        return Objects.equals(left, right);
    }

    // Whether a row in airtable still exists in your database
    protected boolean has(String fieldValue) {
        return database.get(fieldValue) != null;
    }
};
// Async run it every 6 hours.
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
ses.scheduleAtFixedRate(mirror, 0, 6, TimeUnit.HOURS);

Gradle Dependencies

compile group: 'dev.fuxing', name: 'airtable-api', version: '0.3.2'
compile group: 'dev.fuxing', name: 'airtable-mirror', version: '0.3.2'

Testing

https://airtable.com/shrTMCxjhQIF2ZJDe is used to run test against a real instance

Publishing

Since PGP key is required, and it's my key that I rather it not being stored on a server. I will be publishing manually instead.