Thanks to visit codestin.com
Credit goes to pkg.go.dev

airbyte

package module
v0.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 1, 2022 License: MIT Imports: 11 Imported by: 2

README

Airbyte - Golang SDK/CDK

This package aims to help developers build connectors (sources/destinations) really fast in Go. The focus of this package is developer efficiency. It focusses on letting developers focus more on connector business logic instead of airbyte protocol knowledge.

Installation

go get github.com/bitstrapped/airbyte

Docs

Usage

By Example
  1. The fastest way to get started it to look at the full example in examples/httpsource or the Example in the godoc
Detailed Usage
  1. Define a source by implementing the Source interface.
// Source is the only interface you need to define to create your 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
}
  1. Inside of main, pass your source into the sourcerunner
func main() {
	fsrc := filesource.NewFileSource("foobar.txt")
	runner := airbyte.NewSourceRunner(fsrc)
	err := runner.Start()
	if err != nil {
		log.Fatal(err)
	}
}
  1. Write a dockerfile (sample below)
FROM golang:1.17-buster as build

WORKDIR /base
ADD . /base/
RUN go build -o /base/app .


LABEL io.airbyte.version=0.0.1
LABEL io.airbyte.name=airbyte/source

ENTRYPOINT ["/base/app"]
  1. Push to your docker repository and profit!
Contributors

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)
	}
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func UnmarshalFromPath

func UnmarshalFromPath(path string, v interface{}) error

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

const (
	LogLevelFatal LogLevel = "FATAL"
	LogLevelError LogLevel = "ERROR"
	LogLevelWarn  LogLevel = "WARN"
	LogLevelInfo  LogLevel = "INFO"
	LogLevelDebug LogLevel = "DEBUG"
	LogLevelTrace LogLevel = "TRACE"
)

type LogTracker

type LogTracker struct {
	Log LogWriter
}

LogTracker is a single struct which holds a tracker which can be used for logs

type LogWriter

type LogWriter func(level LogLevel, s string) error

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

const (
	String  PropType = "string"
	Number  PropType = "number"
	Integer PropType = "integer"
	Object  PropType = "object"
	Array   PropType = "array"
	Null    PropType = "null"
)

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

type RecordWriter func(v interface{}, streamName string, namespace string) error

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"

type SyncMode

type SyncMode string

SyncMode defines the modes that your source is able to sync in

const (
	// SyncModeFullRefresh means the data will be wiped and fully synced on each run
	SyncModeFullRefresh SyncMode = "full_refresh"
	// SyncModeIncremental is used for incremental syncs
	SyncModeIncremental SyncMode = "incremental"
)

Directories

Path Synopsis
examples
httpsource command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL