multiwoven-integrations 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +34 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +43 -0
  9. data/Rakefile +12 -0
  10. data/lib/multiwoven/integrations/config.rb +13 -0
  11. data/lib/multiwoven/integrations/core/base_connector.rb +44 -0
  12. data/lib/multiwoven/integrations/core/constants.rb +34 -0
  13. data/lib/multiwoven/integrations/core/destination_connector.rb +12 -0
  14. data/lib/multiwoven/integrations/core/http_client.rb +34 -0
  15. data/lib/multiwoven/integrations/core/source_connector.rb +12 -0
  16. data/lib/multiwoven/integrations/core/utils.rb +68 -0
  17. data/lib/multiwoven/integrations/destination/klaviyo/client.rb +119 -0
  18. data/lib/multiwoven/integrations/destination/klaviyo/config/catalog.json +124 -0
  19. data/lib/multiwoven/integrations/destination/klaviyo/config/meta.json +15 -0
  20. data/lib/multiwoven/integrations/destination/klaviyo/config/spec.json +22 -0
  21. data/lib/multiwoven/integrations/protocol/protocol.json +189 -0
  22. data/lib/multiwoven/integrations/protocol/protocol.rb +179 -0
  23. data/lib/multiwoven/integrations/rollout.rb +17 -0
  24. data/lib/multiwoven/integrations/service.rb +57 -0
  25. data/lib/multiwoven/integrations/source/bigquery/client.rb +86 -0
  26. data/lib/multiwoven/integrations/source/bigquery/config/meta.json +15 -0
  27. data/lib/multiwoven/integrations/source/bigquery/config/spec.json +28 -0
  28. data/lib/multiwoven/integrations/source/redshift/client.rb +100 -0
  29. data/lib/multiwoven/integrations/source/redshift/config/meta.json +15 -0
  30. data/lib/multiwoven/integrations/source/redshift/config/spec.json +74 -0
  31. data/lib/multiwoven/integrations/source/snowflake/client.rb +84 -0
  32. data/lib/multiwoven/integrations/source/snowflake/config/meta.json +15 -0
  33. data/lib/multiwoven/integrations/source/snowflake/config/spec.json +87 -0
  34. data/lib/multiwoven/integrations.rb +41 -0
  35. data/multiwoven-integrations.gemspec +54 -0
  36. data/sig/multiwoven/integrations.rbs +6 -0
  37. metadata +291 -0
@@ -0,0 +1,15 @@
1
+ {
2
+ "data":
3
+ {
4
+ "name": "BigQuery",
5
+ "connector_type": "source",
6
+ "connector_subtype": "database",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "source-bigquery",
9
+ "icon": "bigquery.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/sources/bigquery",
3
+ "stream_type": "dynamic",
4
+ "connection_specification": {
5
+ "$schema": "http://json-schema.org/draft-07/schema#",
6
+ "title": "BigQuery Source Spec",
7
+ "type": "object",
8
+ "required": ["project_id", "credentials_json"],
9
+ "properties": {
10
+ "project_id": {
11
+ "type": "string",
12
+ "description": "The GCP project ID for the project containing the target BigQuery dataset.",
13
+ "title": "Project ID"
14
+ },
15
+ "dataset_id": {
16
+ "type": "string",
17
+ "description": "The dataset ID to search for tables and views. If you are only loading data from one dataset, setting this option could result in much faster schema discovery.",
18
+ "title": "Default Dataset ID"
19
+ },
20
+ "credentials_json": {
21
+ "type": "string",
22
+ "description": "The contents of your Service Account Key JSON file. See the <a href=\"https://docs.multiwoven.com/integrations/sources/bigquery#setup-the-bigquery-source-in-multiwoven\">docs</a> for more information on how to obtain this key.",
23
+ "title": "Credentials JSON",
24
+ "multiwoven_secret": true
25
+ }
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg"
4
+
5
+ module Multiwoven::Integrations::Source
6
+ module Redshift
7
+ include Multiwoven::Integrations::Core
8
+ class Client < SourceConnector
9
+ def check_connection(connection_config)
10
+ create_connection(connection_config)
11
+ ConnectionStatus.new(
12
+ status: ConnectionStatusType["succeeded"]
13
+ ).to_multiwoven_message
14
+ rescue PG::Error => e
15
+ ConnectionStatus.new(
16
+ status: ConnectionStatusType["failed"], message: e.message
17
+ ).to_multiwoven_message
18
+ end
19
+
20
+ def discover(connection_config)
21
+ query = "SELECT table_name, column_name, data_type, is_nullable
22
+ FROM information_schema.columns
23
+ WHERE table_schema = '#{connection_config[:schema]}' AND table_catalog = '#{connection_config[:database]}'
24
+ ORDER BY table_name, ordinal_position;"
25
+
26
+ db = create_connection(connection_config)
27
+ records = db.exec(query) do |result|
28
+ result.map do |row|
29
+ row
30
+ end
31
+ end
32
+ catalog = Catalog.new(streams: create_streams(records))
33
+ catalog.to_multiwoven_message
34
+ rescue StandardError => e
35
+ handle_exception(
36
+ "REDSHIFT:DISCOVER:EXCEPTION",
37
+ "error",
38
+ e
39
+ )
40
+ ensure
41
+ db&.close
42
+ end
43
+
44
+ def read(sync_config)
45
+ connection_config = sync_config.source.connection_specification
46
+ query = sync_config.model.query
47
+ db = create_connection(connection_config)
48
+ records = db.exec(query) do |result|
49
+ result.map do |row|
50
+ RecordMessage.new(data: row, emitted_at: Time.now.to_i).to_multiwoven_message
51
+ end
52
+ end
53
+ records
54
+ rescue StandardError => e
55
+ handle_exception(
56
+ "REDSHIFT:READ:EXCEPTION",
57
+ "error",
58
+ e
59
+ )
60
+ ensure
61
+ db&.close
62
+ end
63
+
64
+ private
65
+
66
+ def create_connection(connection_config)
67
+ raise "Unsupported Auth type" unless connection_config[:credentials][:auth_type] == "username/password"
68
+
69
+ PG.connect(
70
+ host: connection_config[:host],
71
+ dbname: connection_config[:database],
72
+ user: connection_config[:credentials][:username],
73
+ password: connection_config[:credentials][:password],
74
+ port: connection_config[:port]
75
+ )
76
+ end
77
+
78
+ def create_streams(records)
79
+ group_by_table(records).map do |r|
80
+ Multiwoven::Integrations::Protocol::Stream.new(name: r[:tablename], action: StreamAction["fetch"], json_schema: convert_to_json_schema(r[:columns]))
81
+ end
82
+ end
83
+
84
+ def group_by_table(records)
85
+ records.group_by { |entry| entry["table_name"] }.map do |table_name, columns|
86
+ {
87
+ tablename: table_name,
88
+ columns: columns.map do |column|
89
+ {
90
+ column_name: column["column_name"],
91
+ type: column["data_type"],
92
+ optional: column["is_nullable"] == "YES"
93
+ }
94
+ end
95
+ }
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,15 @@
1
+ {
2
+ "data":
3
+ {
4
+ "name": "Redshift",
5
+ "connector_type": "source",
6
+ "connector_subtype": "database",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "source-redshift",
9
+ "icon": "redshift.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/sources/redshift",
3
+ "stream_type": "dynamic",
4
+ "connection_specification": {
5
+ "$schema": "http://json-schema.org/draft-07/schema#",
6
+ "title": "Redshift Source Spec",
7
+ "type": "object",
8
+ "required": ["host", "port", "database", "schema"],
9
+ "properties": {
10
+ "credentials": {
11
+ "title": "Authorization Method",
12
+ "type": "object",
13
+ "oneOf": [
14
+ {
15
+ "title": "Username and Password",
16
+ "type": "object",
17
+ "required": ["username", "password", "auth_type"],
18
+ "order": 1,
19
+ "properties": {
20
+ "auth_type": {
21
+ "type": "string",
22
+ "const": "username/password",
23
+ "order": 0
24
+ },
25
+ "username": {
26
+ "description": "The username for Redshift database access.",
27
+ "examples": ["REDSHIFT_USER"],
28
+ "type": "string",
29
+ "title": "Username",
30
+ "order": 1
31
+ },
32
+ "password": {
33
+ "description": "The password for the Redshift user.",
34
+ "type": "string",
35
+ "multiwoven_secret": true,
36
+ "title": "Password",
37
+ "order": 2
38
+ }
39
+ }
40
+ }
41
+ ],
42
+ "order": 0
43
+ },
44
+ "host": {
45
+ "description": "The host endpoint of the Redshift cluster.",
46
+ "examples": ["example-redshift-cluster.abcdefg.us-west-2.redshift.amazonaws.com"],
47
+ "type": "string",
48
+ "title": "Host",
49
+ "order": 1
50
+ },
51
+ "port": {
52
+ "description": "The port on which Redshift is running (default is 5439).",
53
+ "examples": ["5439"],
54
+ "type": "string",
55
+ "title": "Port",
56
+ "order": 2
57
+ },
58
+ "database": {
59
+ "description": "The specific Redshift database to connect to.",
60
+ "examples": ["REDSHIFT_DB"],
61
+ "type": "string",
62
+ "title": "Database",
63
+ "order": 3
64
+ },
65
+ "schema": {
66
+ "description": "The schema within the Redshift database.",
67
+ "examples": ["REDSHIFT_SCHEMA"],
68
+ "type": "string",
69
+ "title": "Schema",
70
+ "order": 4
71
+ }
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven::Integrations::Source
4
+ module Snowflake
5
+ include Multiwoven::Integrations::Core
6
+ class Client < SourceConnector
7
+ def check_connection(connection_config)
8
+ create_connection(connection_config)
9
+ ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
10
+ rescue Sequel::DatabaseConnectionError => e
11
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: e.message).to_multiwoven_message
12
+ end
13
+
14
+ def discover(connection_config)
15
+ query = "SELECT table_name, column_name, data_type, is_nullable
16
+ FROM information_schema.columns
17
+ WHERE table_schema = \'#{connection_config[:schema]}\' AND table_catalog = \'#{connection_config[:database]}\'
18
+ ORDER BY table_name, ordinal_position;"
19
+
20
+ db = create_connection(connection_config)
21
+
22
+ records = []
23
+ db.fetch(query.gsub("\n", "")) do |row|
24
+ records << row
25
+ end
26
+ catalog = Catalog.new(streams: create_streams(records))
27
+ catalog.to_multiwoven_message
28
+ rescue StandardError => e
29
+ handle_exception(
30
+ "SNOWFLAKE:DISCOVER:EXCEPTION",
31
+ "error",
32
+ e
33
+ )
34
+ end
35
+
36
+ def read(sync_config)
37
+ connection_config = sync_config.source.connection_specification
38
+ query = sync_config.model.query
39
+ db = create_connection(connection_config)
40
+
41
+ records = []
42
+ db.fetch(query) do |row|
43
+ records << RecordMessage.new(data: row, emitted_at: Time.now.to_i).to_multiwoven_message
44
+ end
45
+
46
+ records
47
+ rescue StandardError => e
48
+ handle_exception(
49
+ "SNOWFLAKE:READ:EXCEPTION",
50
+ "error",
51
+ e
52
+ )
53
+ end
54
+
55
+ private
56
+
57
+ def create_connection(connection_config)
58
+ raise "Unsupported Auth type" if connection_config[:credentials][:auth_type] != "username/password"
59
+
60
+ Sequel.odbc(drvconnect: generate_drvconnect(connection_config))
61
+ end
62
+
63
+ def generate_drvconnect(connection_config)
64
+ c = connection_config[:credentials]
65
+ "driver=#{SNOWFLAKE_DRIVER_PATH};server=#{connection_config[:host]};uid=#{c[:username]};pwd=#{c[:password]};schema=#{connection_config[:schema]};database=#{connection_config[:database]};warehouse=#{connection_config[:warehouse]};"
66
+ end
67
+
68
+ def create_streams(records)
69
+ group_by_table(records).map do |r|
70
+ Multiwoven::Integrations::Protocol::Stream.new(name: r[:tablename], action: StreamAction["fetch"], json_schema: convert_to_json_schema(r[:columns]))
71
+ end
72
+ end
73
+
74
+ def group_by_table(records)
75
+ records.group_by { |entry| entry[:table_name] }.map do |table_name, columns|
76
+ {
77
+ tablename: table_name,
78
+ columns: columns.map { |column| { column_name: column[:column_name], type: column[:data_type], optional: column[:is_nullable] == "YES" } }
79
+ }
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ {
2
+ "data":
3
+ {
4
+ "name": "Snowflake",
5
+ "connector_type": "source",
6
+ "connector_subtype": "database",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "source-snowflake",
9
+ "icon": "snowflake.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
@@ -0,0 +1,87 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/sources/snowflake",
3
+ "stream_type": "dynamic",
4
+ "connection_specification": {
5
+ "$schema": "http://json-schema.org/draft-07/schema#",
6
+ "title": "Snowflake Source Spec",
7
+ "type": "object",
8
+ "required": ["host", "role", "warehouse", "database"],
9
+ "properties": {
10
+ "credentials": {
11
+ "title": "Authorization Method",
12
+ "type": "object",
13
+ "oneOf": [
14
+ {
15
+ "title": "Username and Password",
16
+ "type": "object",
17
+ "required": ["username", "password", "auth_type"],
18
+ "order": 1,
19
+ "properties": {
20
+ "auth_type": {
21
+ "type": "string",
22
+ "const": "username/password",
23
+ "order": 0
24
+ },
25
+ "username": {
26
+ "description": "The username you created to allow multiwoven to access the database.",
27
+ "examples": ["MULTIWOVEN_USER"],
28
+ "type": "string",
29
+ "title": "Username",
30
+ "order": 1
31
+ },
32
+ "password": {
33
+ "description": "The password associated with the username.",
34
+ "type": "string",
35
+ "multiwoven_secret": true,
36
+ "title": "Password",
37
+ "order": 2
38
+ }
39
+ }
40
+ }
41
+ ],
42
+ "order": 0
43
+ },
44
+ "host": {
45
+ "description": "The host domain of the snowflake instance (must include the account, region, cloud environment, and end with snowflakecomputing.com).",
46
+ "examples": ["accountname.us-east-2.aws.snowflakecomputing.com"],
47
+ "type": "string",
48
+ "title": "Account Name",
49
+ "order": 1
50
+ },
51
+ "role": {
52
+ "description": "The role you created for multiwoven to access Snowflake.",
53
+ "examples": ["MULTIWOVEN_ROLE"],
54
+ "type": "string",
55
+ "title": "Role",
56
+ "order": 2
57
+ },
58
+ "warehouse": {
59
+ "description": "The warehouse you created for multiwoven to access data.",
60
+ "examples": ["MULTIWOVEN_WAREHOUSE"],
61
+ "type": "string",
62
+ "title": "Warehouse",
63
+ "order": 3
64
+ },
65
+ "database": {
66
+ "description": "The database you created for multiwoven to access data.",
67
+ "examples": ["MULTIWOVEN_DATABASE"],
68
+ "type": "string",
69
+ "title": "Database",
70
+ "order": 4
71
+ },
72
+ "schema": {
73
+ "description": "The source Snowflake schema tables. Leave empty to access tables from multiple schemas.",
74
+ "examples": ["MULTIWOVEN_SCHEMA"],
75
+ "type": "string",
76
+ "title": "Schema",
77
+ "order": 5
78
+ },
79
+ "jdbc_url_params": {
80
+ "description": "Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (example: key1=value1&key2=value2&key3=value3).",
81
+ "title": "JDBC URL Params",
82
+ "type": "string",
83
+ "order": 6
84
+ }
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "dry-struct"
5
+ require "dry-schema"
6
+ require "dry-types"
7
+ require "odbc"
8
+ require "sequel"
9
+ require "byebug"
10
+ require "net/http"
11
+ require "uri"
12
+ require "active_support/core_ext/hash/indifferent_access"
13
+
14
+ # Service
15
+ require_relative "integrations/config"
16
+ require_relative "integrations/rollout"
17
+ require_relative "integrations/service"
18
+
19
+ # Core
20
+ require_relative "integrations/core/constants"
21
+ require_relative "integrations/core/utils"
22
+ require_relative "integrations/protocol/protocol"
23
+ require_relative "integrations/core/base_connector"
24
+ require_relative "integrations/core/source_connector"
25
+ require_relative "integrations/core/destination_connector"
26
+ require_relative "integrations/core/http_client"
27
+
28
+ # Source
29
+ require_relative "integrations/source/snowflake/client"
30
+ require_relative "integrations/source/redshift/client"
31
+ require_relative "integrations/source/bigquery/client"
32
+
33
+ # Destination
34
+ require_relative "integrations/destination/klaviyo/client"
35
+
36
+ module Multiwoven
37
+ module Integrations
38
+ class Error < StandardError; end
39
+ # Your code goes here...
40
+ end
41
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/multiwoven/integrations/rollout"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "multiwoven-integrations"
7
+ spec.version = Multiwoven::Integrations::VERSION
8
+ spec.authors = ["Subin T P"]
9
+ spec.email = ["subin@multiwoven.com"]
10
+
11
+ spec.summary = "Open source data activation"
12
+ spec.description = "Wrapper of connectors"
13
+ spec.homepage = "https://www.multiwoven.com/"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ # spec.metadata["allowed_push_host"] = nil
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://www.multiwoven.com/"
21
+ spec.metadata["changelog_uri"] = "https://www.multiwoven.com/"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) ||
28
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
29
+ end
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ spec.add_runtime_dependency "activesupport"
36
+ spec.add_runtime_dependency "dry-schema"
37
+ spec.add_runtime_dependency "dry-struct"
38
+ spec.add_runtime_dependency "dry-types"
39
+ spec.add_runtime_dependency "google-cloud-bigquery"
40
+ spec.add_runtime_dependency "pg"
41
+ spec.add_runtime_dependency "rake"
42
+ spec.add_runtime_dependency "ruby-odbc", "~> 0.999992"
43
+ spec.add_runtime_dependency "sequel"
44
+
45
+ spec.add_development_dependency "byebug"
46
+ spec.add_development_dependency "rspec"
47
+ spec.add_development_dependency "rubocop"
48
+ spec.add_development_dependency "simplecov"
49
+ spec.add_development_dependency "simplecov_json_formatter"
50
+ spec.add_development_dependency "webmock"
51
+
52
+ # For more information and examples about making a new gem, check out our
53
+ # guide at: https://bundler.io/guides/creating_gem.html
54
+ end
@@ -0,0 +1,6 @@
1
+ module Multiwoven
2
+ module Integrations
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end