Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/fuxingloh/airtable

A lightweight Java 8 Airtable API client for https://airtable.com/api with all features implemented.
https://github.com/fuxingloh/airtable

airtable airtable-api java java-api

Last synced: 16 days ago
JSON representation

A lightweight Java 8 Airtable API client for https://airtable.com/api with all features implemented.

Awesome Lists containing this project

README

        

# Airtable Java API Interface

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

[![CI](https://github.com/fuxingloh/airtable/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/fuxingloh/airtable/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/fuxingloh/airtable/branch/master/graph/badge.svg?token=GF8N55YZQ1)](https://codecov.io/gh/fuxingloh/airtable)
[![maven-central](https://img.shields.io/maven-central/v/dev.fuxing/airtable-api)](https://search.maven.org/artifact/dev.fuxing/airtable-api)

# Features:
* Supports the new batch API
* Supports all fields
* Supports all features exposed in https://airtable.com/api (as of 2020/01/20)
* Build in pagination support
* Heavily documented (javadoc)
* Fluent Query Builder for type safe query building
* `AirtableFormula` fluent builder
* `AirtableTable.QuerySpec` fluent builder
* Lightweight
* `commons-lang3` mainly for FastDateFormat, java 8 version is non thread-safe
* `fluent-hc` to perform REST call
* `jackson-databind` for handling JSON data
* Proper airtable & client exception handling
* `AirtableApiException` (from api service: https://api.airtable.com)
* `AirtableClientException` (from client: most likely your mistake)
* Status 429 Backoff 30 seconds auto try
* Customizable HTTP Client (fluent-hc)
* Custom Module: Cache using Guava
* Custom Module: Data Mirroring (e.g. ETL, Lake, MR)

# Download
Hosted in Maven Central.
### Maven
```xml

dev.fuxing
airtable-api
0.3.1

```
### Gradle
```groovy
compile group: 'dev.fuxing', name: 'airtable-api', version: '0.3.2'
```

# Example
#### Getting the AirtableTable interface.
```java
AirtableApi api = new AirtableApi("key...");
AirtableTable table = api.base("app...").table("Table Name");
```

#### List
Querying and getting a list.

```java
// 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.
```java
table.iterator().forEachRemaining(record -> {
List images = record.getFieldAttachmentList("Images");
String name = record.getFieldString("Name");
});
```

#### Query Spec Builder
All list querystring is supported with functional fluent formula builder.

```java
List 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
```java
AirtableRecord record = table.get("rec...");
```

#### Creating a new record
```java
AirtableRecord record = new AirtableRecord();
record.putField("Name", "Posted");

// Attachment support
CollaboratorField collaborator = new CollaboratorField();
collaborator.setEmail("[email protected]");
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
```java
AirtableRecord record = new AirtableRecord();
record.setId("rec...");
record.putField("Name", "Patched");

record = table.patch(record);
```

#### Replacing an existing record
```java
AirtableRecord record = new AirtableRecord();
record.setId("rec...");
record.putField("Name", "Replaced Entirely");

record = table.put(record);
```

#### Deleting an existing record
```java
table.delete("rec...");
```

### 429 Auto Retry
Auto retry is enabled by default. To disable it, you can create an `Executor` without retry.
```java
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.
```java
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.
```java
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.
```java
List results = cache.query(querySpec -> {
querySpec.filterByFormula(LogicalOperator.EQ, field("Name"), value("Name 1"));
});
```

#### Gradle Dependencies
```groovy
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:
* You need a quick and dirty way to look at data from your database.
* Your product manager don't know how to use SQL.
* You don't want to manually create scripts and generate them.
* You like Airtable and how simple it is.
* You want to use Airtable blocks to generate Analytic.
* You want visibility of internal data.
* You are irritated from the requests your pm requires. (sort by?, you want me to filter WHAT?, why can't you learn how to use group by!)

#### Implementation
```java
// Example Database
Database database = new Database();

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

return new Iterator() {
@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
```groovy
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

- `./gradlew uploadArchives closeAndPromoteRepository`
- Requires `NEXUS_USERNAME` & `NEXUS_PASSWORD` & PGP key.

> 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.