attio 0.1.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.
- checksums.yaml +7 -0
- data/.codecov.yml +52 -0
- data/.github/CODEOWNERS +28 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +28 -0
- data/.github/dependabot.yml +54 -0
- data/.github/pull_request_template.md +40 -0
- data/.github/workflows/ci.yml +112 -0
- data/.github/workflows/dependabot-auto-merge.yml +45 -0
- data/.github/workflows/pr_checks.yml +145 -0
- data/.github/workflows/release.yml +147 -0
- data/.gitignore +10 -0
- data/.rspec +4 -0
- data/.rubocop.yml +133 -0
- data/.yardopts +18 -0
- data/CHANGELOG.md +34 -0
- data/CODE_OF_CONDUCT.md +37 -0
- data/CONTRIBUTING.md +280 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +127 -0
- data/LICENSE.txt +21 -0
- data/README.md +292 -0
- data/Rakefile +57 -0
- data/SECURITY.md +59 -0
- data/attio.gemspec +34 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/danger/Dangerfile +121 -0
- data/docs/.nojekyll +0 -0
- data/docs/Attio/AuthenticationError.html +152 -0
- data/docs/Attio/Client.html +1373 -0
- data/docs/Attio/ConnectionPool/TimeoutError.html +148 -0
- data/docs/Attio/ConnectionPool.html +944 -0
- data/docs/Attio/Error.html +152 -0
- data/docs/Attio/HttpClient/ConnectionError.html +152 -0
- data/docs/Attio/HttpClient/TimeoutError.html +152 -0
- data/docs/Attio/HttpClient.html +1329 -0
- data/docs/Attio/Logger.html +747 -0
- data/docs/Attio/NotFoundError.html +152 -0
- data/docs/Attio/RateLimitError.html +152 -0
- data/docs/Attio/RequestLogger.html +780 -0
- data/docs/Attio/Resources/Attributes.html +508 -0
- data/docs/Attio/Resources/Base.html +624 -0
- data/docs/Attio/Resources/Lists.html +1002 -0
- data/docs/Attio/Resources/Objects.html +415 -0
- data/docs/Attio/Resources/Records.html +1465 -0
- data/docs/Attio/Resources/Users.html +415 -0
- data/docs/Attio/Resources/Workspaces.html +324 -0
- data/docs/Attio/Resources.html +141 -0
- data/docs/Attio/RetryHandler.html +1023 -0
- data/docs/Attio/ServerError.html +152 -0
- data/docs/Attio/ValidationError.html +152 -0
- data/docs/Attio.html +397 -0
- data/docs/SETUP.md +117 -0
- data/docs/_index.html +378 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/example.rb +119 -0
- data/docs/file.CHANGELOG.html +124 -0
- data/docs/file.README.html +371 -0
- data/docs/file_list.html +64 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +371 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +750 -0
- data/docs/top-level-namespace.html +110 -0
- data/lib/attio/client.rb +118 -0
- data/lib/attio/connection_pool.rb +69 -0
- data/lib/attio/errors.rb +9 -0
- data/lib/attio/http_client.rb +100 -0
- data/lib/attio/logger.rb +110 -0
- data/lib/attio/resources/attributes.rb +26 -0
- data/lib/attio/resources/base.rb +55 -0
- data/lib/attio/resources/lists.rb +56 -0
- data/lib/attio/resources/objects.rb +20 -0
- data/lib/attio/resources/records.rb +158 -0
- data/lib/attio/resources/users.rb +20 -0
- data/lib/attio/resources/workspaces.rb +13 -0
- data/lib/attio/retry_handler.rb +70 -0
- data/lib/attio/version.rb +3 -0
- data/lib/attio.rb +60 -0
- data/run_tests.rb +52 -0
- data/test_basic.rb +51 -0
- data/test_typhoeus.rb +31 -0
- metadata +160 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>
|
7
|
+
Top Level Namespace
|
8
|
+
|
9
|
+
— Attio Ruby Client Documentation
|
10
|
+
|
11
|
+
</title>
|
12
|
+
|
13
|
+
<link rel="stylesheet" href="css/style.css" type="text/css" />
|
14
|
+
|
15
|
+
<link rel="stylesheet" href="css/common.css" type="text/css" />
|
16
|
+
|
17
|
+
<script type="text/javascript">
|
18
|
+
pathId = "";
|
19
|
+
relpath = '';
|
20
|
+
</script>
|
21
|
+
|
22
|
+
|
23
|
+
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
|
24
|
+
|
25
|
+
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
|
26
|
+
|
27
|
+
|
28
|
+
</head>
|
29
|
+
<body>
|
30
|
+
<div class="nav_wrap">
|
31
|
+
<iframe id="nav" src="class_list.html?1"></iframe>
|
32
|
+
<div id="resizer"></div>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div id="main" tabindex="-1">
|
36
|
+
<div id="header">
|
37
|
+
<div id="menu">
|
38
|
+
|
39
|
+
<a href="_index.html">Index</a> »
|
40
|
+
|
41
|
+
|
42
|
+
<span class="title">Top Level Namespace</span>
|
43
|
+
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div id="search">
|
47
|
+
|
48
|
+
<a class="full_list_link" id="class_list_link"
|
49
|
+
href="class_list.html">
|
50
|
+
|
51
|
+
<svg width="24" height="24">
|
52
|
+
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
|
53
|
+
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
|
54
|
+
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
|
55
|
+
</svg>
|
56
|
+
</a>
|
57
|
+
|
58
|
+
</div>
|
59
|
+
<div class="clear"></div>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<div id="content"><h1>Top Level Namespace
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
</h1>
|
67
|
+
<div class="box_info">
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
</div>
|
80
|
+
|
81
|
+
<h2>Defined Under Namespace</h2>
|
82
|
+
<p class="children">
|
83
|
+
|
84
|
+
|
85
|
+
<strong class="modules">Modules:</strong> <span class='object_link'><a href="Attio.html" title="Attio (module)">Attio</a></span>
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
</p>
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
</div>
|
101
|
+
|
102
|
+
<div id="footer">
|
103
|
+
Generated on Mon Aug 11 11:26:43 2025 by
|
104
|
+
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
105
|
+
0.9.37 (ruby-3.4.5).
|
106
|
+
</div>
|
107
|
+
|
108
|
+
</div>
|
109
|
+
</body>
|
110
|
+
</html>
|
data/lib/attio/client.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
module Attio
|
2
|
+
# The main client class for interacting with the Attio API.
|
3
|
+
#
|
4
|
+
# This class provides access to all Attio API resources and handles
|
5
|
+
# authentication, connection management, and request routing.
|
6
|
+
#
|
7
|
+
# @example Basic client creation
|
8
|
+
# client = Attio::Client.new(api_key: 'your-api-key')
|
9
|
+
#
|
10
|
+
# @example Custom timeout
|
11
|
+
# client = Attio::Client.new(api_key: 'your-api-key', timeout: 60)
|
12
|
+
#
|
13
|
+
# @author Ernest Sim
|
14
|
+
# @since 1.0.0
|
15
|
+
class Client
|
16
|
+
# The base URL for the Attio API v2
|
17
|
+
API_BASE_URL = "https://api.attio.com/v2".freeze
|
18
|
+
|
19
|
+
# Default request timeout in seconds
|
20
|
+
DEFAULT_TIMEOUT = 30
|
21
|
+
|
22
|
+
# @return [String] The API key used for authentication
|
23
|
+
attr_reader :api_key
|
24
|
+
|
25
|
+
# @return [Integer] The request timeout in seconds
|
26
|
+
attr_reader :timeout
|
27
|
+
|
28
|
+
# Initialize a new Attio API client.
|
29
|
+
#
|
30
|
+
# @param api_key [String] Your Attio API key (required)
|
31
|
+
# @param timeout [Integer] Request timeout in seconds (default: 30)
|
32
|
+
# @raise [ArgumentError] if api_key is nil or empty
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# client = Attio::Client.new(api_key: 'sk-...your-key...')
|
36
|
+
def initialize(api_key:, timeout: DEFAULT_TIMEOUT)
|
37
|
+
raise ArgumentError, "API key is required" if api_key.nil? || api_key.empty?
|
38
|
+
|
39
|
+
@api_key = api_key
|
40
|
+
@timeout = timeout
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the HTTP connection instance for making API requests.
|
44
|
+
#
|
45
|
+
# This method creates and configures the HTTP client with proper
|
46
|
+
# authentication headers and settings. The connection is cached
|
47
|
+
# for subsequent requests.
|
48
|
+
#
|
49
|
+
# @return [HttpClient] The configured HTTP client instance
|
50
|
+
# @api private
|
51
|
+
def connection
|
52
|
+
@connection ||= HttpClient.new(
|
53
|
+
base_url: API_BASE_URL,
|
54
|
+
headers: {
|
55
|
+
"Authorization" => "Bearer #{api_key}",
|
56
|
+
"Accept" => "application/json",
|
57
|
+
"Content-Type" => "application/json",
|
58
|
+
"User-Agent" => "Attio Ruby Client/#{VERSION}"
|
59
|
+
},
|
60
|
+
timeout: timeout
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Access to the Records API resource.
|
65
|
+
#
|
66
|
+
# @return [Resources::Records] Records resource instance
|
67
|
+
# @example
|
68
|
+
# records = client.records.list(object: 'people')
|
69
|
+
def records
|
70
|
+
@records ||= Resources::Records.new(self)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Access to the Objects API resource.
|
74
|
+
#
|
75
|
+
# @return [Resources::Objects] Objects resource instance
|
76
|
+
# @example
|
77
|
+
# objects = client.objects.list
|
78
|
+
def objects
|
79
|
+
@objects ||= Resources::Objects.new(self)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Access to the Lists API resource.
|
83
|
+
#
|
84
|
+
# @return [Resources::Lists] Lists resource instance
|
85
|
+
# @example
|
86
|
+
# lists = client.lists.list
|
87
|
+
def lists
|
88
|
+
@lists ||= Resources::Lists.new(self)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Access to the Workspaces API resource.
|
92
|
+
#
|
93
|
+
# @return [Resources::Workspaces] Workspaces resource instance
|
94
|
+
# @example
|
95
|
+
# workspaces = client.workspaces.list
|
96
|
+
def workspaces
|
97
|
+
@workspaces ||= Resources::Workspaces.new(self)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Access to the Attributes API resource.
|
101
|
+
#
|
102
|
+
# @return [Resources::Attributes] Attributes resource instance
|
103
|
+
# @example
|
104
|
+
# attributes = client.attributes.list
|
105
|
+
def attributes
|
106
|
+
@attributes ||= Resources::Attributes.new(self)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Access to the Users API resource.
|
110
|
+
#
|
111
|
+
# @return [Resources::Users] Users resource instance
|
112
|
+
# @example
|
113
|
+
# users = client.users.list
|
114
|
+
def users
|
115
|
+
@users ||= Resources::Users.new(self)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Attio
|
4
|
+
class ConnectionPool
|
5
|
+
DEFAULT_POOL_SIZE = 5
|
6
|
+
DEFAULT_TIMEOUT = 5 # seconds to wait for connection
|
7
|
+
|
8
|
+
attr_reader :size, :timeout
|
9
|
+
|
10
|
+
def initialize(size: DEFAULT_POOL_SIZE, timeout: DEFAULT_TIMEOUT, &block)
|
11
|
+
@size = size
|
12
|
+
@timeout = timeout
|
13
|
+
@available = Queue.new
|
14
|
+
@key = :"#{object_id}_connection"
|
15
|
+
@block = block
|
16
|
+
@mutex = Mutex.new
|
17
|
+
|
18
|
+
size.times { @available << create_connection }
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_connection
|
22
|
+
connection = checkout
|
23
|
+
begin
|
24
|
+
yield connection
|
25
|
+
ensure
|
26
|
+
checkin(connection)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def checkout
|
31
|
+
deadline = Time.now + timeout
|
32
|
+
|
33
|
+
loop do
|
34
|
+
return @available.pop(true) if @available.size > 0
|
35
|
+
|
36
|
+
if Time.now >= deadline
|
37
|
+
raise TimeoutError, "Couldn't acquire connection within #{timeout} seconds"
|
38
|
+
end
|
39
|
+
|
40
|
+
sleep(0.01)
|
41
|
+
end
|
42
|
+
rescue ThreadError
|
43
|
+
raise TimeoutError, "No connections available"
|
44
|
+
end
|
45
|
+
|
46
|
+
def checkin(connection)
|
47
|
+
@mutex.synchronize do
|
48
|
+
@available << connection if connection
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def shutdown
|
53
|
+
@mutex.synchronize do
|
54
|
+
@available.close
|
55
|
+
while connection = @available.pop(true) rescue nil
|
56
|
+
connection.close if connection.respond_to?(:close)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def create_connection
|
64
|
+
@block.call
|
65
|
+
end
|
66
|
+
|
67
|
+
class TimeoutError < StandardError; end
|
68
|
+
end
|
69
|
+
end
|
data/lib/attio/errors.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Attio
|
5
|
+
class HttpClient
|
6
|
+
DEFAULT_TIMEOUT = 30
|
7
|
+
|
8
|
+
attr_reader :base_url, :headers, :timeout
|
9
|
+
|
10
|
+
def initialize(base_url:, headers: {}, timeout: DEFAULT_TIMEOUT)
|
11
|
+
@base_url = base_url
|
12
|
+
@headers = headers
|
13
|
+
@timeout = timeout
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(path, params = {})
|
17
|
+
execute_request(:get, path, params: params)
|
18
|
+
end
|
19
|
+
|
20
|
+
def post(path, body = {})
|
21
|
+
execute_request(:post, path, body: body.to_json)
|
22
|
+
end
|
23
|
+
|
24
|
+
def patch(path, body = {})
|
25
|
+
execute_request(:patch, path, body: body.to_json)
|
26
|
+
end
|
27
|
+
|
28
|
+
def put(path, body = {})
|
29
|
+
execute_request(:put, path, body: body.to_json)
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete(path)
|
33
|
+
execute_request(:delete, path)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def execute_request(method, path, options = {})
|
39
|
+
url = "#{base_url}/#{path}"
|
40
|
+
|
41
|
+
request_options = {
|
42
|
+
method: method,
|
43
|
+
headers: headers.merge('Content-Type' => 'application/json'),
|
44
|
+
timeout: timeout,
|
45
|
+
connecttimeout: timeout
|
46
|
+
}.merge(options)
|
47
|
+
|
48
|
+
request = Typhoeus::Request.new(url, request_options)
|
49
|
+
response = request.run
|
50
|
+
|
51
|
+
handle_response(response)
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle_response(response)
|
55
|
+
case response.code
|
56
|
+
when 0
|
57
|
+
# Timeout or connection error
|
58
|
+
if response.timed_out?
|
59
|
+
raise TimeoutError, "Request timed out"
|
60
|
+
else
|
61
|
+
raise ConnectionError, "Connection failed: #{response.return_message}"
|
62
|
+
end
|
63
|
+
when 200..299
|
64
|
+
parse_json(response.body)
|
65
|
+
when 401
|
66
|
+
raise AuthenticationError, parse_error_message(response)
|
67
|
+
when 404
|
68
|
+
raise NotFoundError, parse_error_message(response)
|
69
|
+
when 422
|
70
|
+
raise ValidationError, parse_error_message(response)
|
71
|
+
when 429
|
72
|
+
raise RateLimitError, parse_error_message(response)
|
73
|
+
when 500..599
|
74
|
+
raise ServerError, parse_error_message(response)
|
75
|
+
else
|
76
|
+
raise Error, "Request failed with status #{response.code}: #{parse_error_message(response)}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_json(body)
|
81
|
+
return {} if body.nil? || body.empty?
|
82
|
+
JSON.parse(body)
|
83
|
+
rescue JSON::ParserError => e
|
84
|
+
raise Error, "Invalid JSON response: #{e.message}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_error_message(response)
|
88
|
+
body = parse_json(response.body) rescue response.body
|
89
|
+
|
90
|
+
if body.is_a?(Hash)
|
91
|
+
body["error"] || body["message"] || body.to_s
|
92
|
+
else
|
93
|
+
body.to_s
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class TimeoutError < Error; end
|
98
|
+
class ConnectionError < Error; end
|
99
|
+
end
|
100
|
+
end
|
data/lib/attio/logger.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Attio
|
5
|
+
class Logger < ::Logger
|
6
|
+
def initialize(logdev, level: ::Logger::INFO, formatter: nil)
|
7
|
+
super(logdev)
|
8
|
+
self.level = level
|
9
|
+
self.formatter = formatter || default_formatter
|
10
|
+
end
|
11
|
+
|
12
|
+
def debug(message, **context)
|
13
|
+
super(format_message(message, context))
|
14
|
+
end
|
15
|
+
|
16
|
+
def info(message, **context)
|
17
|
+
super(format_message(message, context))
|
18
|
+
end
|
19
|
+
|
20
|
+
def warn(message, **context)
|
21
|
+
super(format_message(message, context))
|
22
|
+
end
|
23
|
+
|
24
|
+
def error(message, **context)
|
25
|
+
super(format_message(message, context))
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def format_message(message, context)
|
31
|
+
return message if context.empty?
|
32
|
+
|
33
|
+
{
|
34
|
+
message: message,
|
35
|
+
**context
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_formatter
|
40
|
+
proc do |severity, datetime, progname, msg|
|
41
|
+
data = {
|
42
|
+
timestamp: datetime.iso8601,
|
43
|
+
level: severity,
|
44
|
+
progname: progname
|
45
|
+
}
|
46
|
+
|
47
|
+
if msg.is_a?(Hash)
|
48
|
+
data.merge!(msg)
|
49
|
+
else
|
50
|
+
data[:message] = msg
|
51
|
+
end
|
52
|
+
|
53
|
+
"#{JSON.generate(data)}\n"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class RequestLogger
|
59
|
+
attr_reader :logger, :log_level
|
60
|
+
|
61
|
+
def initialize(logger:, log_level: :debug)
|
62
|
+
@logger = logger
|
63
|
+
@log_level = log_level
|
64
|
+
end
|
65
|
+
|
66
|
+
def log_request(method, url, headers, body)
|
67
|
+
return unless logger
|
68
|
+
|
69
|
+
logger.send(log_level, "API Request",
|
70
|
+
method: method.to_s.upcase,
|
71
|
+
url: url,
|
72
|
+
headers: sanitize_headers(headers),
|
73
|
+
body: sanitize_body(body)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def log_response(response, duration)
|
78
|
+
return unless logger
|
79
|
+
|
80
|
+
logger.send(log_level, "API Response",
|
81
|
+
status: response.code,
|
82
|
+
duration_ms: (duration * 1000).round(2),
|
83
|
+
headers: response.headers,
|
84
|
+
body_size: response.body&.bytesize
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def sanitize_headers(headers)
|
91
|
+
headers.transform_values do |value|
|
92
|
+
if value.include?('Bearer')
|
93
|
+
value.gsub(/Bearer\s+[\w\-]+/, 'Bearer [REDACTED]')
|
94
|
+
else
|
95
|
+
value
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def sanitize_body(body)
|
101
|
+
return nil unless body
|
102
|
+
|
103
|
+
if body.is_a?(String) && body.length > 1000
|
104
|
+
"#{body[0..1000]}... (truncated)"
|
105
|
+
else
|
106
|
+
body
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Attio
|
2
|
+
module Resources
|
3
|
+
class Attributes < Base
|
4
|
+
def list(object:, **params)
|
5
|
+
validate_object!(object)
|
6
|
+
request(:get, "objects/#{object}/attributes", params)
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(object:, id_or_slug:)
|
10
|
+
validate_object!(object)
|
11
|
+
validate_id_or_slug!(id_or_slug)
|
12
|
+
request(:get, "objects/#{object}/attributes/#{id_or_slug}")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate_object!(object)
|
18
|
+
raise ArgumentError, "Object type is required" if object.nil? || object.to_s.strip.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_id_or_slug!(id_or_slug)
|
22
|
+
raise ArgumentError, "Attribute ID or slug is required" if id_or_slug.nil? || id_or_slug.to_s.strip.empty?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Attio
|
2
|
+
module Resources
|
3
|
+
# Base class for all API resource classes.
|
4
|
+
#
|
5
|
+
# This class provides common functionality and request handling
|
6
|
+
# for all Attio API resource implementations.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @author Ernest Sim
|
10
|
+
# @since 1.0.0
|
11
|
+
class Base
|
12
|
+
# @return [Client] The API client instance
|
13
|
+
attr_reader :client
|
14
|
+
|
15
|
+
# Initialize a new resource instance.
|
16
|
+
#
|
17
|
+
# @param client [Client] The API client instance
|
18
|
+
# @raise [ArgumentError] if client is nil
|
19
|
+
def initialize(client)
|
20
|
+
raise ArgumentError, "Client is required" unless client
|
21
|
+
@client = client
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Make an HTTP request to the API.
|
27
|
+
#
|
28
|
+
# @param method [Symbol] The HTTP method (:get, :post, :patch, :put, :delete)
|
29
|
+
# @param path [String] The API endpoint path
|
30
|
+
# @param params [Hash] Request parameters (default: {})
|
31
|
+
#
|
32
|
+
# @return [Hash] The API response
|
33
|
+
# @raise [ArgumentError] if method is unsupported
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def request(method, path, params = {})
|
37
|
+
case method
|
38
|
+
when :get
|
39
|
+
client.connection.get(path, params)
|
40
|
+
when :post
|
41
|
+
client.connection.post(path, params)
|
42
|
+
when :patch
|
43
|
+
client.connection.patch(path, params)
|
44
|
+
when :put
|
45
|
+
client.connection.put(path, params)
|
46
|
+
when :delete
|
47
|
+
client.connection.delete(path)
|
48
|
+
else
|
49
|
+
raise ArgumentError, "Unsupported HTTP method: #{method}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Attio
|
2
|
+
module Resources
|
3
|
+
class Lists < Base
|
4
|
+
def list(**params)
|
5
|
+
request(:get, "lists", params)
|
6
|
+
end
|
7
|
+
|
8
|
+
def get(id:)
|
9
|
+
validate_id!(id)
|
10
|
+
request(:get, "lists/#{id}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def entries(id:, **params)
|
14
|
+
validate_id!(id)
|
15
|
+
request(:get, "lists/#{id}/entries", params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_entry(id:, data:)
|
19
|
+
validate_id!(id)
|
20
|
+
validate_data!(data)
|
21
|
+
request(:post, "lists/#{id}/entries", data)
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_entry(list_id:, entry_id:)
|
25
|
+
validate_list_id!(list_id)
|
26
|
+
validate_entry_id!(entry_id)
|
27
|
+
request(:get, "lists/#{list_id}/entries/#{entry_id}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_entry(list_id:, entry_id:)
|
31
|
+
validate_list_id!(list_id)
|
32
|
+
validate_entry_id!(entry_id)
|
33
|
+
request(:delete, "lists/#{list_id}/entries/#{entry_id}")
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def validate_id!(id)
|
39
|
+
raise ArgumentError, "List ID is required" if id.nil? || id.to_s.strip.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate_list_id!(list_id)
|
43
|
+
raise ArgumentError, "List ID is required" if list_id.nil? || list_id.to_s.strip.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_entry_id!(entry_id)
|
47
|
+
raise ArgumentError, "Entry ID is required" if entry_id.nil? || entry_id.to_s.strip.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_data!(data)
|
51
|
+
raise ArgumentError, "Data is required" if data.nil?
|
52
|
+
raise ArgumentError, "Data must be a hash" unless data.is_a?(Hash)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Attio
|
2
|
+
module Resources
|
3
|
+
class Objects < Base
|
4
|
+
def list(**params)
|
5
|
+
request(:get, "objects", params)
|
6
|
+
end
|
7
|
+
|
8
|
+
def get(id_or_slug:)
|
9
|
+
validate_id_or_slug!(id_or_slug)
|
10
|
+
request(:get, "objects/#{id_or_slug}")
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def validate_id_or_slug!(id_or_slug)
|
16
|
+
raise ArgumentError, "Object ID or slug is required" if id_or_slug.nil? || id_or_slug.to_s.strip.empty?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|