act_as_api_client 1.0.0 → 1.3.1

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.
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Run it with `ruby github_repositories.rb`
4
+
5
+ require "bundler/inline"
6
+
7
+ gemfile(true) do
8
+ source "https://rubygems.org"
9
+
10
+ gem "act_as_api_client", path: "./"
11
+ gem "tabled"
12
+ end
13
+
14
+ require "act_as_api_client"
15
+
16
+ class GithubApiClient < ApiClient
17
+ act_as_api_client for: :github_repositories
18
+ end
19
+
20
+ github_api_client = GithubApiClient.new
21
+ pp github_api_client.find("Rukomoynikov/tabled")
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActAsApiClient
4
+ class ClientLoader
5
+ attr_reader :client_for
6
+
7
+ def initialize(client_for:)
8
+ @client_for = client_for
9
+ end
10
+
11
+ def class_name
12
+ case client_for.class.to_s
13
+ when "String", "Symbol"
14
+ "#{convert_underscore_to_camelcase(client_for)}Client"
15
+ when "Array"
16
+ client_for
17
+ .map { |item| convert_underscore_to_camelcase(item) }
18
+ .join("::")
19
+ .concat("Client")
20
+ end
21
+ end
22
+
23
+ def path
24
+ case client_for.class.to_s
25
+ when "String", "Symbol"
26
+ File.expand_path("clients/#{client_for}_client", File.dirname(__FILE__))
27
+ when "Array"
28
+ client_path = client_for.join("/")
29
+
30
+ File.expand_path("clients/#{client_path}_client", File.dirname(__FILE__))
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Converting from authorize_net_webhooks_client to AuthorizeNetWebhooksClient
37
+ #
38
+ # @param value [String] string with underscores
39
+ # @return [String] transformed in camel case format string
40
+ def convert_underscore_to_camelcase(value)
41
+ value.to_s.capitalize.gsub(/_(.)/) do |s|
42
+ s.upcase.gsub("_", "")
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "act_as_api_client/clients/http/simple_client"
4
+ require "act_as_api_client/clients/http/streaming_client"
5
+
6
+ module ActAsApiClient
7
+ module Clients
8
+ module Anthropic
9
+ module MessagesClient
10
+ ANTHROPIC_VERSION = "2023-06-01"
11
+ BASE_URL = "https://api.anthropic.com/v1/messages"
12
+
13
+ # Sends request to Anthropic API https://docs.anthropic.com/en/api/messages
14
+ #
15
+ # @param model [String] The model that will complete your prompt
16
+ # @param messages [Array] Input messages
17
+ # @param max_tokens [Integer] The maximum number of tokens to generate before stopping
18
+ # @param metadata [Hash] An object describing metadata about the request
19
+ # @param stop_sequences [Array<String>] Custom text sequences that will cause the model to stop generating
20
+ # @param stream [Boolean] Whether to incrementally stream the response using server-sent events
21
+ # @param system [String] System prompt
22
+ # @param temperature [Float] Amount of randomness injected into the response
23
+ # @param tool_choice [Hash] How the model should use the provided tools
24
+ # @param tools [Array<Hash>] Definitions of tools that the model may use
25
+ # @param top_k [Integer] Only sample from the top K options for each subsequent token
26
+ # @param top_p [Float] Use nucleus sampling
27
+ #
28
+ # @return [Hash] Response from Anthropic API
29
+ def create(**params, &block)
30
+ client = if params.fetch(:stream, false)
31
+ ActAsApiClient::Clients::Http::StreamingClient.new
32
+ else
33
+ ActAsApiClient::Clients::Http::SimpleClient.new
34
+ end
35
+
36
+ client.post(BASE_URL,
37
+ headers: headers,
38
+ stream: params.fetch(:stream, false),
39
+ body: body(**params),
40
+ &block)
41
+ end
42
+
43
+ private
44
+
45
+ def headers
46
+ default = {
47
+ "Content-Type": "application/json",
48
+ "x-api-key" => options[:x_api_key],
49
+ "anthropic-version" => options[:anthropic_version] || ANTHROPIC_VERSION
50
+ }
51
+
52
+ default["anthropic-beta"] = options[:anthropic_beta] if options[:anthropic_beta]
53
+
54
+ default
55
+ end
56
+
57
+ # rubocop:disable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/MethodLength
58
+ def body(model:,
59
+ messages:,
60
+ max_tokens:,
61
+ metadata: nil,
62
+ stop_sequences: nil,
63
+ stream: false,
64
+ system: nil,
65
+ temperature: 1.0,
66
+ tool_choice: nil,
67
+ tools: nil,
68
+ top_k: nil,
69
+ top_p: nil)
70
+ default = {
71
+ model: model,
72
+ messages: messages,
73
+ max_tokens: max_tokens,
74
+ stream: stream,
75
+ temperature: temperature
76
+ }
77
+
78
+ default[:metadata] = metadata if metadata
79
+ default[:stop_sequences] = stop_sequences if stop_sequences
80
+ default[:system] = system if system
81
+ default[:tool_choice] = tool_choice if tool_choice
82
+ default[:tools] = tools if tools
83
+ default[:top_k] = top_k if top_k
84
+ default[:top_p] = top_p if top_p
85
+
86
+ default
87
+ end
88
+ # rubocop:enable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/MethodLength
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "http_client"
3
+ require "act_as_api_client/clients/http/simple_client"
4
4
 
5
5
  module ActAsApiClient
6
6
  module Clients
7
7
  module GithubRepositoriesClient
8
- include HttpClient
9
-
10
8
  # Searches Github for one repository by it's owner and repository names.
11
9
  # More details look at the corresponding {https://docs.github.com/en/rest/repos/repos#get-a-repository Github Docs page}
12
10
  #
@@ -18,9 +16,11 @@ module ActAsApiClient
18
16
  raise StandardError, "repository_name parameter is not valid"
19
17
  end
20
18
 
21
- get("https://api.github.com/repos/#{repository_name}",
22
- headers: { "Accept" => "application/vnd.github.v3+json",
23
- "Authorization" => (options[:token] ? "token #{options[:token]}" : nil) })
19
+ http_client = ActAsApiClient::Clients::Http::SimpleClient.new
20
+
21
+ http_client.get("https://api.github.com/repos/#{repository_name}",
22
+ headers: { "Accept" => "application/vnd.github.v3+json",
23
+ "Authorization" => (options[:token] ? "token #{options[:token]}" : nil) })
24
24
  end
25
25
 
26
26
  # Search through Github repositories using query string and additional parameters explained on the {https://docs.github.com/en/rest/search#search-repositories Github Docs page}
@@ -38,10 +38,12 @@ module ActAsApiClient
38
38
  #
39
39
  # @return [Array] list of repositories
40
40
  def where(query_string, parameters = {})
41
- get("https://api.github.com/search/repositories",
42
- headers: { "Accept" => "application/vnd.github.v3+json",
43
- "Authorization" => (options[:token] ? "token #{options[:token]}" : nil) },
44
- params: { q: query_string }.merge(parameters))
41
+ http_client = ActAsApiClient::Clients::Http::SimpleClient.new
42
+
43
+ http_client.get("https://api.github.com/search/repositories",
44
+ headers: { "Accept" => "application/vnd.github.v3+json",
45
+ "Authorization" => (options[:token] ? "token #{options[:token]}" : nil) },
46
+ params: { q: query_string }.merge(parameters))
45
47
  end
46
48
 
47
49
  # Use this method if you need to get list of repositories selected by one of this conditions: user, authenticated_user, organization
@@ -72,23 +74,12 @@ module ActAsApiClient
72
74
  "https://api.github.com/repositories"
73
75
  end
74
76
 
75
- get(url,
76
- headers: { "Accept" => "application/vnd.github.v3+json",
77
- "Authorization" => (options[:token] ? "token #{options[:token]}" : nil) })
78
- end
77
+ http_client = ActAsApiClient::Clients::Http::SimpleClient.new
79
78
 
80
- # def delete
81
- # # Call only on queried before repository and repository is not 404/400 and has right to delete (write)
82
- # "delete"
83
- # end
84
- #
85
- # def create
86
- # "create"
87
- # end
88
- #
89
- # def update
90
- # "update"
91
- # end
79
+ http_client.get(url,
80
+ headers: { "Accept" => "application/vnd.github.v3+json",
81
+ "Authorization" => (options[:token] ? "token #{options[:token]}" : nil) })
82
+ end
92
83
  end
93
84
  end
94
85
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActAsApiClient
4
+ module Clients
5
+ module Http
6
+ module BaseClient
7
+ private
8
+
9
+ def build_uri(url, **options)
10
+ uri = URI(url)
11
+ uri.query = URI.encode_www_form(options.fetch(:params, {}))
12
+
13
+ uri
14
+ end
15
+
16
+ def set_request_headers(headers:, request:)
17
+ headers.each do |key, value|
18
+ request[key] = value
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+
6
+ require "act_as_api_client/errors/invalid_response_error"
7
+ require "act_as_api_client/errors/network_error"
8
+
9
+ require "act_as_api_client/clients/http/base_client"
10
+
11
+ module ActAsApiClient
12
+ module Clients
13
+ module Http
14
+ class SimpleClient
15
+ include BaseClient
16
+
17
+ def get(url, **options)
18
+ uri = build_uri(url, **options)
19
+
20
+ request = Net::HTTP::Get.new(uri)
21
+ set_request_headers(headers: options.fetch(:headers, {}),
22
+ request: request)
23
+
24
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
25
+ http.request(request)
26
+ end
27
+
28
+ parse_response(response)
29
+ rescue Net::OpenTimeout, Net::ReadTimeout, SocketError => e
30
+ raise ActAsApiClient::Errors::NetworkError, "Network error occurred: #{e.message}"
31
+ end
32
+
33
+ def post(url, headers: {}, **options)
34
+ uri = build_uri(url, **options)
35
+ request = Net::HTTP::Post.new(uri)
36
+ set_request_headers(headers: headers, request: request)
37
+ request.body = options.fetch(:body, {}).to_json
38
+
39
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
40
+ http.request(request)
41
+ end
42
+
43
+ parse_response(response)
44
+ rescue Net::OpenTimeout, Net::ReadTimeout, SocketError => e
45
+ raise ActAsApiClient::Errors::NetworkError, "Network error occurred: #{e.message}"
46
+ end
47
+
48
+ private
49
+
50
+ def parse_response(response)
51
+ case response
52
+ when Net::HTTPSuccess, Net::HTTPBadRequest
53
+ ::JSON.parse(response.body)
54
+ when Net::HTTPNotFound, Net::HTTPUnprocessableEntity, Net::HTTPUnauthorized
55
+ ::JSON.parse(response.body)
56
+ end
57
+ rescue JSON::ParserError => e
58
+ raise ActAsApiClient::Errors::InvalidResponseError, "Invalid JSON response: #{e.message}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "event_stream_parser"
6
+
7
+ require "act_as_api_client/errors/invalid_response_error"
8
+ require "act_as_api_client/errors/network_error"
9
+
10
+ module ActAsApiClient
11
+ module Clients
12
+ module Http
13
+ class StreamingClient
14
+ include BaseClient
15
+
16
+ def post(url, headers: {}, **options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
17
+ uri = URI(url)
18
+ uri.query = URI.encode_www_form(options.fetch(:params, {}))
19
+
20
+ request = Net::HTTP::Post.new(uri)
21
+
22
+ request.body = options.fetch(:body, {}).to_json
23
+ set_request_headers(headers: headers, request: request)
24
+
25
+ parser = EventStreamParser::Parser.new
26
+
27
+ Net::HTTP.start(uri.hostname, use_ssl: true) do |http|
28
+ http.request(request) do |response|
29
+ case response
30
+ when Net::HTTPSuccess, Net::HTTPBadRequest
31
+ response.read_body do |chunk|
32
+ parser.feed(chunk) do |_type, data, _id, _reconnection_time|
33
+ yield(::JSON.parse(data)) if block_given?
34
+ end
35
+ end
36
+ when Net::HTTPNotFound, Net::HTTPUnprocessableEntity, Net::HTTPUnauthorized
37
+ response.read_body do |chunk|
38
+ parser.feed(chunk) do |_type, data, _id, _reconnection_time|
39
+ yield(::JSON.parse(data)) if block_given?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ rescue Net::OpenTimeout, Net::ReadTimeout, SocketError => e
46
+ raise ActAsApiClient::Errors::NetworkError, "Network error occurred: #{e.message}"
47
+ rescue JSON::ParserError => e
48
+ raise ActAsApiClient::Errors::InvalidResponseError, "Invalid JSON response: #{e.message}"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActAsApiClient
4
+ module Errors
5
+ class InvalidResponseError < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActAsApiClient
4
+ module Errors
5
+ class NetworkError < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActAsApiClient
4
+ module Errors
5
+ class NonExistingClient < StandardError
6
+ def initialize(requested_client:)
7
+ @requested_client = requested_client
8
+ super
9
+ end
10
+
11
+ def message
12
+ "The requested client (#{@requested_client}) doesn't exist"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "act_as_api_client/base_api_methods"
3
+ require "act_as_api_client/errors/non_existing_client_error"
4
+ require "act_as_api_client/client_loader"
4
5
 
5
6
  class ApiClient
6
- include ActAsApiClient::BaseApiMethods
7
-
8
7
  class << self
9
8
  def act_as_api_client(**args)
10
9
  set_general_client(client_for: args.fetch(:for, nil))
@@ -16,26 +15,16 @@ class ApiClient
16
15
  def set_general_client(client_for:)
17
16
  return if client_for.nil?
18
17
 
19
- class_name = convert_underscore_to_camelcase(client_for)
18
+ client_loader = ActAsApiClient::ClientLoader.new(client_for: client_for)
20
19
 
21
- require(File.expand_path("act_as_api_client/clients/#{client_for}_client",
22
- File.dirname(__FILE__)))
23
- include const_get("ActAsApiClient::Clients::#{class_name}Client")
20
+ require(client_loader.path)
21
+ include const_get("ActAsApiClient::Clients::#{client_loader.class_name}")
22
+ rescue LoadError
23
+ raise ActAsApiClient::Errors::NonExistingClient.new(requested_client: client_for)
24
24
  end
25
25
 
26
26
  def set_options(options:)
27
27
  define_method("options") { options }
28
28
  end
29
-
30
- # Converting from authorize_net_webhooks_client to AuthorizeNetWebhooksClient
31
- #
32
- # @param value [String] string with underscores
33
- #
34
- # @return [String] transformed in camel case format string
35
- def convert_underscore_to_camelcase(value)
36
- value.to_s.capitalize.gsub(/_(.)/) do |s|
37
- s.upcase.gsub("_", "")
38
- end
39
- end
40
29
  end
41
30
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: act_as_api_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Rukomoynikov
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-22 00:00:00.000000000 Z
11
+ date: 2025-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: event_stream_parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: byebug
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -118,6 +132,7 @@ extra_rdoc_files: []
118
132
  files:
119
133
  - ".devcontainer.json"
120
134
  - ".github/workflows/main.yml"
135
+ - ".github/workflows/update_docs.yml"
121
136
  - ".gitignore"
122
137
  - ".rspec"
123
138
  - ".rubocop.yml"
@@ -132,21 +147,38 @@ files:
132
147
  - act_as_api_client.gemspec
133
148
  - bin/console
134
149
  - bin/setup
135
- - demo.rb
136
150
  - docker-compose.yml
151
+ - docs/.gitignore
152
+ - docs/404.html
153
+ - docs/Gemfile
154
+ - docs/Gemfile.lock
155
+ - docs/_config.yml
156
+ - docs/_config_development.yml
157
+ - docs/assets/thumbnail.jpg
158
+ - docs/clients/anthropic.markdown
159
+ - docs/docker-compose.yml
160
+ - docs/index.markdown
161
+ - examples/anthropic_playground.rb
162
+ - examples/github_repositories.rb
137
163
  - lib/act_as_api_client.rb
138
- - lib/act_as_api_client/base_api_methods.rb
164
+ - lib/act_as_api_client/client_loader.rb
165
+ - lib/act_as_api_client/clients/anthropic/messages_client.rb
139
166
  - lib/act_as_api_client/clients/github_repositories_client.rb
140
- - lib/act_as_api_client/clients/http_client.rb
167
+ - lib/act_as_api_client/clients/http/base_client.rb
168
+ - lib/act_as_api_client/clients/http/simple_client.rb
169
+ - lib/act_as_api_client/clients/http/streaming_client.rb
170
+ - lib/act_as_api_client/errors/invalid_response_error.rb
171
+ - lib/act_as_api_client/errors/network_error.rb
172
+ - lib/act_as_api_client/errors/non_existing_client_error.rb
141
173
  homepage: https://rubygems.org/gems/act_as_api_client
142
174
  licenses:
143
175
  - MIT
144
176
  metadata:
145
177
  source_code_uri: https://github.com/Rukomoynikov/act_as_api_client
146
178
  homepage_uri: https://rubygems.org/gems/act_as_api_client
147
- documentation_uri: https://rubydoc.info/github/Rukomoynikov/act_as_api_client/main
179
+ documentation_uri: https://rukomoynikov.github.io/act_as_api_client/
148
180
  bug_tracker_uri: https://github.com/Rukomoynikov/act_as_api_client/issues
149
- post_install_message:
181
+ post_install_message:
150
182
  rdoc_options: []
151
183
  require_paths:
152
184
  - lib
@@ -161,8 +193,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
193
  - !ruby/object:Gem::Version
162
194
  version: '0'
163
195
  requirements: []
164
- rubygems_version: 3.0.3.1
165
- signing_key:
196
+ rubygems_version: 3.3.27
197
+ signing_key:
166
198
  specification_version: 4
167
199
  summary: Collection of predefined API clients
168
200
  test_files: []
data/demo.rb DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/act_as_api_client"
4
-
5
- class GithubClient < ApiClient
6
- act_as_api_client for: :github_repositories
7
- end
8
-
9
- p GithubClient.ancestors
10
- p GithubClient.new.method(:find).source_location
11
-
12
- GithubClient.new.find
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActAsApiClient
4
- module BaseApiMethods
5
- def find
6
- raise StandardError, "Should be defined in inherited class"
7
- end
8
-
9
- def where
10
- raise StandardError, "Should be defined in inherited class"
11
- end
12
-
13
- def find_by
14
- raise StandardError, "Should be defined in inherited class"
15
- end
16
-
17
- def delete
18
- raise StandardError, "Should be defined in inherited class"
19
- end
20
-
21
- def create
22
- raise StandardError, "Should be defined in inherited class"
23
- end
24
-
25
- def update
26
- raise StandardError, "Should be defined in inherited class"
27
- end
28
- end
29
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "json"
5
-
6
- module ActAsApiClient
7
- module Clients
8
- module HttpClient
9
- private
10
-
11
- def get(url, options = {})
12
- # Request part
13
- uri = URI(url)
14
- uri.query = URI.encode_www_form(options.fetch(:params, {}))
15
-
16
- request = Net::HTTP::Get.new(uri)
17
- request_headers(headers: options.fetch(:headers, {}),
18
- request: request)
19
-
20
- # Response part
21
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
22
- http.request(request)
23
- end
24
-
25
- case response
26
- when Net::HTTPNotFound, Net::HTTPSuccess, Net::HTTPUnprocessableEntity, Net::HTTPUnauthorized
27
- ::JSON.parse(response.body)
28
- end
29
- end
30
-
31
- def post
32
- # HTTParty.post
33
- end
34
-
35
- def put
36
- # HTTParty.put
37
- end
38
-
39
- def update
40
- # HTTParty.update
41
- end
42
-
43
- def delete
44
- # HTTParty.delete
45
- end
46
-
47
- def request_headers(headers:, request:)
48
- headers.each do |key, value|
49
- request[key] = value
50
- end
51
- end
52
- end
53
- end
54
- end