multiwoven-integrations 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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