Documentation
¶
Overview ¶
Airbyte is the go-sdk/cdk to help build connectors quickly in go This package abstracts away much of the "protocol" away from the user and lets them focus on biz logic It focuses on developer efficiency and tries to be strongly typed as much as possible to help dev's move fast without mistakes
Example ¶
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/bitstrapped/airbyte"
)
type HTTPSource struct {
baseURL string
}
type LastSyncTime struct {
Timestamp int64 `json:"timestamp"`
}
type HTTPConfig struct {
APIKey string `json:"apiKey"`
}
func NewHTTPSource(baseURL string) airbyte.Source {
return HTTPSource{
baseURL: baseURL,
}
}
func (h HTTPSource) Spec(logTracker airbyte.LogTracker) (*airbyte.ConnectorSpecification, error) {
logTracker.Log(airbyte.LogLevelInfo, "Running Spec")
return &airbyte.ConnectorSpecification{
DocumentationURL: "https://bitstrapped.com",
ChangeLogURL: "https://bitstrapped.com",
SupportsIncremental: false,
SupportsNormalization: true,
SupportsDBT: true,
SupportedDestinationSyncModes: []airbyte.DestinationSyncMode{
airbyte.DestinationSyncModeOverwrite,
},
ConnectionSpecification: airbyte.ConnectionSpecification{
Title: "Example HTTP Source",
Description: "This is an example http source for the docs's",
Type: "object",
Required: []airbyte.PropertyName{"apiKey"},
Properties: airbyte.Properties{
Properties: map[airbyte.PropertyName]airbyte.PropertySpec{
"apiKey": {
Description: "api key to access http source, valid uuid",
Examples: []string{"xxxx-xxxx-xxxx-xxxx"},
PropertyType: airbyte.PropertyType{
Type: []airbyte.PropType{
airbyte.String,
},
},
},
},
},
},
}, nil
}
func (h HTTPSource) Check(srcCfgPath string, logTracker airbyte.LogTracker) error {
logTracker.Log(airbyte.LogLevelDebug, "validating api connection")
var srcCfg HTTPConfig
err := airbyte.UnmarshalFromPath(srcCfgPath, &srcCfg)
if err != nil {
return err
}
resp, err := http.Get(fmt.Sprintf("%s/ping?key=%s", h.baseURL, srcCfg.APIKey))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New("Invalid status")
}
return nil
}
func (h HTTPSource) Discover(srcCfgPath string, logTracker airbyte.LogTracker) (*airbyte.Catalog, error) {
var srcCfg HTTPConfig
err := airbyte.UnmarshalFromPath(srcCfgPath, &srcCfg)
if err != nil {
return nil, err
}
return &airbyte.Catalog{Streams: []airbyte.Stream{{
Name: "users",
JSONSchema: airbyte.Properties{
Properties: map[airbyte.PropertyName]airbyte.PropertySpec{
"userid": {
PropertyType: airbyte.PropertyType{
Type: []airbyte.PropType{airbyte.Integer, airbyte.Null},
AirbyteType: airbyte.BigInteger},
Description: "user ID - see the big int",
},
"name": {
PropertyType: airbyte.PropertyType{
Type: []airbyte.PropType{airbyte.String, airbyte.Null},
},
Description: "user name",
},
},
},
SupportedSyncModes: []airbyte.SyncMode{
airbyte.SyncModeFullRefresh,
},
SourceDefinedCursor: false,
Namespace: "bitstrapped",
},
{
Name: "payments",
JSONSchema: airbyte.InferSchemaFromStruct(Payment{}, logTracker),
SupportedSyncModes: []airbyte.SyncMode{
airbyte.SyncModeFullRefresh,
},
SourceDefinedCursor: false,
Namespace: "bitstrapped",
},
}}, nil
}
type User struct {
UserID int64 `json:"userid"`
Name string `json:"name"`
}
type Payment struct {
UserID int64 `json:"userid"`
PaymentAmount int64 `json:"paymentAmount"`
}
func (h HTTPSource) Read(sourceCfgPath string, prevStatePath string, configuredCat *airbyte.ConfiguredCatalog,
tracker airbyte.MessageTracker) error {
tracker.Log(airbyte.LogLevelInfo, "Running read")
var src HTTPConfig
err := airbyte.UnmarshalFromPath(sourceCfgPath, &src)
if err != nil {
return err
}
// see if there is a last sync
var st LastSyncTime
airbyte.UnmarshalFromPath(sourceCfgPath, &st)
if st.Timestamp <= 0 {
st.Timestamp = -1
}
for _, stream := range configuredCat.Streams {
if stream.Stream.Name == "users" {
var u []User
uri := fmt.Sprintf("https://api.bistrapped.com/users?apiKey=%s", src.APIKey)
resp, err := http.Get(uri)
if err != nil {
return err
}
err = json.NewDecoder(resp.Body).Decode(&u)
if err != nil {
return err
}
for _, ur := range u {
err := tracker.Record(ur, stream.Stream.Name, stream.Stream.Namespace)
if err != nil {
return err
}
}
}
if stream.Stream.Name == "payments" {
var p []Payment
uri := fmt.Sprintf("%s/payments?apiKey=%s", h.baseURL, src.APIKey)
resp, err := http.Get(uri)
if err != nil {
return err
}
err = json.NewDecoder(resp.Body).Decode(&p)
if err != nil {
return err
}
for _, py := range p {
err := tracker.Record(py, stream.Stream.Name, stream.Stream.Namespace)
if err != nil {
return err
}
}
}
}
tracker.State(&LastSyncTime{
Timestamp: time.Now().UnixMilli(),
})
return nil
}
func main() {
hsrc := NewHTTPSource("https://api.bitstrapped.com")
runner := airbyte.NewSourceRunner(hsrc, os.Stdout)
err := runner.Start()
if err != nil {
log.Fatal(err)
}
}
Output:
Index ¶
- func UnmarshalFromPath(path string, v interface{}) error
- type AirbytePropType
- type Catalog
- type ConfiguredCatalog
- type ConfiguredStream
- type ConnectionSpecification
- type ConnectorSpecification
- type DestinationSyncMode
- type FormatType
- type LogLevel
- type LogTracker
- type LogWriter
- type MessageTracker
- type PropType
- type Properties
- type PropertyName
- type PropertySpec
- type PropertyType
- type RecordWriter
- type Source
- type SourceRunner
- type StateWriter
- type Stream
- type SyncMode
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func UnmarshalFromPath ¶
UnmarshalFromPath is used to unmarshal json files into respective struct's this is most commonly used to unmarshal your State between runs and also unmarshal SourceConfig's
Example usage
type CustomState struct {
Timestamp int `json:"timestamp"`
Foobar string `json:"foobar"`
}
func (s *CustomSource) Read(stPath string, ...) error {
var cs CustomState
err = airbyte.UnmarshalFromPath(stPath, &cs)
if err != nil {
// handle error
}
// cs is populated
}
Types ¶
type AirbytePropType ¶
type AirbytePropType string
AirbytePropType is used to define airbyte specific property types. See more here: https://docs.airbyte.com/understanding-airbyte/supported-data-types
const ( TimestampWithTZ AirbytePropType = "timestamp_with_timezone" TimestampWOTZ AirbytePropType = "timestamp_without_timezone" BigInteger AirbytePropType = "big_integer" BigNumber AirbytePropType = "big_number" )
type Catalog ¶
type Catalog struct {
Streams []Stream `json:"streams"`
}
Catalog defines the complete available schema you can sync with a source This should not be mistaken with ConfiguredCatalog which is the "selected" schema you want to sync
type ConfiguredCatalog ¶
type ConfiguredCatalog struct {
Streams []ConfiguredStream `json:"streams"`
}
ConfiguredCatalog is the "selected" schema you want to sync This should not be mistaken with Catalog which represents the complete available schema to sync
type ConfiguredStream ¶
type ConfiguredStream struct {
Stream Stream `json:"stream"`
SyncMode SyncMode `json:"sync_mode"`
CursorField []string `json:"cursor_field"`
DestinationSyncMode DestinationSyncMode `json:"destination_sync_mode"`
PrimaryKey [][]string `json:"primary_key"`
}
ConfiguredStream defines a single selected stream to sync
type ConnectionSpecification ¶
type ConnectionSpecification struct {
Title string `json:"title"`
Description string `json:"description"`
Properties
Type string `json:"type"` // should always be "object"
Required []PropertyName `json:"required"`
}
ConnectionSpecification is used to define the settings that are configurable "per" instance of your connector
type ConnectorSpecification ¶
type ConnectorSpecification struct {
DocumentationURL string `json:"documentationUrl,omitempty"`
ChangeLogURL string `json:"changeLogUrl"`
SupportsIncremental bool `json:"supportsIncremental"`
SupportsNormalization bool `json:"supportsNormalization"`
SupportsDBT bool `json:"supportsDBT"`
SupportedDestinationSyncModes []DestinationSyncMode `json:"supported_destination_sync_modes"`
ConnectionSpecification ConnectionSpecification `json:"connectionSpecification"`
}
ConnectorSpecification is used to define the connector wide settings. Every connection using your connector will comply to these settings
type DestinationSyncMode ¶
type DestinationSyncMode string
DestinationSyncMode represents how the destination should interpret your data
var ( // DestinationSyncModeAppend is used for the destination to know it needs to append data DestinationSyncModeAppend DestinationSyncMode = "append" // DestinationSyncModeOverwrite is used to indicate the destination should overwrite data DestinationSyncModeOverwrite DestinationSyncMode = "overwrite" )
type FormatType ¶
type FormatType string
FormatType is used to define data type formats supported by airbyte where needed (usually for strings formatted as dates). See more here: https://docs.airbyte.com/understanding-airbyte/supported-data-types
const ( Date FormatType = "date" DateTime FormatType = "datetime" )
type LogLevel ¶
type LogLevel string
LogLevel defines the log levels that can be emitted with airbyte logs
type LogTracker ¶
type LogTracker struct {
Log LogWriter
}
LogTracker is a single struct which holds a tracker which can be used for logs
type LogWriter ¶
LogWriter is exported for documentation purposes - only use this through LogTracker or MessageTracker to ensure thread-safe behavior with the writer
type MessageTracker ¶
type MessageTracker struct {
// State will save an arbitrary JSON blob to airbyte state
State StateWriter
// Record will emit a record (data point) out to airbyte to sync with appropriate timestamps
Record RecordWriter
// Log logs out to airbyte
Log LogWriter
}
MessageTracker is used to encap State tracking, Record tracking and Log tracking It's thread safe
type PropType ¶
type PropType string
PropType defines the property types any field can take. See more here: https://docs.airbyte.com/understanding-airbyte/supported-data-types
type Properties ¶
type Properties struct {
Properties map[PropertyName]PropertySpec `json:"properties"`
}
Properties defines the property map which is used to define any single "field name" along with its specification
func InferSchemaFromStruct ¶ added in v0.0.7
func InferSchemaFromStruct(i interface{}, logTracker LogTracker) Properties
Infer schema translates golang structs to JSONSchema format
type PropertyName ¶
type PropertyName string
PropertyName is a alias for a string to make it clear to the user that the "key" in the map is the name of the property
type PropertySpec ¶
type PropertySpec struct {
Description string `json:"description"`
PropertyType `json:",omitempty"`
Examples []string `json:"examples,omitempty"`
Items map[string]interface{} `json:"items,omitempty"`
Properties map[PropertyName]PropertySpec `json:"properties,omitempty"`
IsSecret bool `json:"airbyte_secret,omitempty"`
}
type PropertyType ¶
type PropertyType struct {
Type []PropType `json:"type,omitempty"`
AirbyteType AirbytePropType `json:"airbyte_type,omitempty"`
}
type RecordWriter ¶
RecordWriter is exported for documentation purposes - only use this through MessageTracker
type Source ¶
type Source interface {
// Spec returns the input "form" spec needed for your source
Spec(logTracker LogTracker) (*ConnectorSpecification, error)
// Check verifies the source - usually verify creds/connection etc.
Check(srcCfgPath string, logTracker LogTracker) error
// Discover returns the schema of the data you want to sync
Discover(srcConfigPath string, logTracker LogTracker) (*Catalog, error)
// Read will read the actual data from your source and use tracker.Record(), tracker.State() and tracker.Log() to sync data with airbyte/destinations
// MessageTracker is thread-safe and so it is completely find to spin off goroutines to sync your data (just don't forget your waitgroups :))
// returning an error from this will cancel the sync and returning a nil from this will successfully end the sync
Read(sourceCfgPath string, prevStatePath string, configuredCat *ConfiguredCatalog,
tracker MessageTracker) error
}
Source is the only interface you need to define to create your source!
type SourceRunner ¶
type SourceRunner struct {
// contains filtered or unexported fields
}
SourceRunner acts as an "orchestrator" of sorts to run your source for you
func NewSourceRunner ¶
func NewSourceRunner(src Source, w io.Writer) SourceRunner
NewSourceRunner takes your defined Source and plugs it in with the rest of airbyte
func (SourceRunner) Start ¶
func (sr SourceRunner) Start() error
Start starts your source Example usage would look like this in your main.go
func() main {
src := newCoolSource()
runner := airbyte.NewSourceRunner(src)
err := runner.Start()
if err != nil {
log.Fatal(err)
}
}
Yes, it really is that easy!
type StateWriter ¶
type StateWriter func(v interface{}) error
StateWriter is exported for documentation purposes - only use this through MessageTracker
type Stream ¶
type Stream struct {
Name string `json:"name"`
JSONSchema Properties `json:"json_schema"`
SupportedSyncModes []SyncMode `json:"supported_sync_modes,omitempty"`
SourceDefinedCursor bool `json:"source_defined_cursor,omitempty"`
DefaultCursorField []string `json:"default_cursor_field,omitempty"`
SourceDefinedPrimaryKey [][]string `json:"source_defined_primary_key,omitempty"`
Namespace string `json:"namespace"`
}
Stream defines a single "schema" you'd like to sync - think of this as a table, collection, topic, etc. In airbyte terminology these are "streams"