attio 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +39 -15
- data/.github/workflows/coverage.yml +67 -0
- data/.github/workflows/pr_checks.yml +25 -7
- data/.github/workflows/release.yml +27 -13
- data/.github/workflows/tests.yml +67 -0
- data/.rubocop.yml +362 -90
- data/CHANGELOG.md +49 -1
- data/CONCEPTS.md +428 -0
- data/CONTRIBUTING.md +4 -4
- data/Gemfile +8 -5
- data/Gemfile.lock +4 -4
- data/README.md +164 -2
- data/Rakefile +8 -6
- data/attio.gemspec +6 -7
- data/danger/Dangerfile +22 -34
- data/docs/example.rb +30 -29
- data/examples/advanced_filtering.rb +178 -0
- data/examples/basic_usage.rb +110 -0
- data/examples/collaboration_example.rb +173 -0
- data/examples/full_workflow.rb +348 -0
- data/examples/notes_and_tasks.rb +200 -0
- data/lib/attio/client.rb +67 -29
- data/lib/attio/connection_pool.rb +26 -14
- data/lib/attio/errors.rb +4 -2
- data/lib/attio/http_client.rb +70 -41
- data/lib/attio/logger.rb +37 -27
- data/lib/attio/resources/attributes.rb +12 -5
- data/lib/attio/resources/base.rb +66 -27
- data/lib/attio/resources/comments.rb +147 -0
- data/lib/attio/resources/lists.rb +21 -24
- data/lib/attio/resources/notes.rb +110 -0
- data/lib/attio/resources/objects.rb +11 -4
- data/lib/attio/resources/records.rb +49 -67
- data/lib/attio/resources/tasks.rb +131 -0
- data/lib/attio/resources/threads.rb +154 -0
- data/lib/attio/resources/users.rb +10 -4
- data/lib/attio/resources/workspaces.rb +9 -1
- data/lib/attio/retry_handler.rb +19 -11
- data/lib/attio/version.rb +3 -1
- data/lib/attio.rb +15 -9
- metadata +13 -18
- data/run_tests.rb +0 -52
- data/test_basic.rb +0 -51
- data/test_typhoeus.rb +0 -31
@@ -1,41 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Handles all record-related API operations.
|
4
|
+
#
|
5
|
+
# Records are the main data entities in Attio, representing things like
|
6
|
+
# people, companies, deals, etc. This class provides methods to create,
|
7
|
+
# read, update, delete, and query records.
|
8
|
+
#
|
9
|
+
# @example List records
|
10
|
+
# records = client.records.list(object: 'people', filters: { name: 'John' })
|
11
|
+
#
|
12
|
+
# @example Create a record
|
13
|
+
# record = client.records.create(
|
14
|
+
# object: 'people',
|
15
|
+
# data: { name: 'Jane Doe', email: 'jane@example.com' }
|
16
|
+
# )
|
17
|
+
#
|
18
|
+
# @author Ernest Sim
|
19
|
+
# @since 1.0.0
|
1
20
|
module Attio
|
2
21
|
module Resources
|
3
|
-
# Handles all record-related API operations.
|
4
|
-
#
|
5
|
-
# Records are the main data entities in Attio, representing things like
|
6
|
-
# people, companies, deals, etc. This class provides methods to create,
|
7
|
-
# read, update, delete, and query records.
|
8
|
-
#
|
9
|
-
# @example List records
|
10
|
-
# records = client.records.list(object: 'people', filters: { name: 'John' })
|
11
|
-
#
|
12
|
-
# @example Create a record
|
13
|
-
# record = client.records.create(
|
14
|
-
# object: 'people',
|
15
|
-
# data: { name: 'Jane Doe', email: 'jane@example.com' }
|
16
|
-
# )
|
17
|
-
#
|
18
|
-
# @author Ernest Sim
|
19
|
-
# @since 1.0.0
|
20
22
|
class Records < Base
|
21
23
|
# Query and list records for a specific object type.
|
22
|
-
#
|
24
|
+
#
|
23
25
|
# This method allows you to retrieve records with optional filtering,
|
24
26
|
# sorting, and pagination parameters.
|
25
|
-
#
|
27
|
+
#
|
26
28
|
# @param object [String] The object type to query (e.g., 'people', 'companies')
|
27
29
|
# @param params [Hash] Query parameters including filters, sorts, and pagination
|
28
30
|
# @option params [Hash] :filters Filtering criteria
|
29
31
|
# @option params [Array] :sorts Sorting options
|
30
32
|
# @option params [Integer] :limit Number of records to return
|
31
33
|
# @option params [String] :cursor Pagination cursor for next page
|
32
|
-
#
|
34
|
+
#
|
33
35
|
# @return [Hash] API response containing records and pagination info
|
34
36
|
# @raise [ArgumentError] if object is nil or empty
|
35
|
-
#
|
37
|
+
#
|
36
38
|
# @example Basic listing
|
37
39
|
# records = client.records.list(object: 'people')
|
38
|
-
#
|
40
|
+
#
|
39
41
|
# @example With filters
|
40
42
|
# records = client.records.list(
|
41
43
|
# object: 'people',
|
@@ -43,34 +45,34 @@ module Attio
|
|
43
45
|
# limit: 50
|
44
46
|
# )
|
45
47
|
def list(object:, **params)
|
46
|
-
|
48
|
+
validate_required_string!(object, "Object type")
|
47
49
|
request(:post, "objects/#{object}/records/query", params)
|
48
50
|
end
|
49
51
|
|
50
52
|
# Retrieve a specific record by ID.
|
51
|
-
#
|
53
|
+
#
|
52
54
|
# @param object [String] The object type (e.g., 'people', 'companies')
|
53
55
|
# @param id [String] The record ID
|
54
|
-
#
|
56
|
+
#
|
55
57
|
# @return [Hash] The record data
|
56
58
|
# @raise [ArgumentError] if object or id is nil or empty
|
57
|
-
#
|
59
|
+
#
|
58
60
|
# @example
|
59
61
|
# record = client.records.get(object: 'people', id: 'abc123')
|
60
62
|
def get(object:, id:)
|
61
|
-
|
62
|
-
validate_id!(id)
|
63
|
+
validate_required_string!(object, "Object type")
|
64
|
+
validate_id!(id, "Record")
|
63
65
|
request(:get, "objects/#{object}/records/#{id}")
|
64
66
|
end
|
65
67
|
|
66
68
|
# Create a new record.
|
67
|
-
#
|
69
|
+
#
|
68
70
|
# @param object [String] The object type to create the record in
|
69
71
|
# @param data [Hash] The record data to create
|
70
|
-
#
|
72
|
+
#
|
71
73
|
# @return [Hash] The created record data
|
72
74
|
# @raise [ArgumentError] if object is nil/empty or data is invalid
|
73
|
-
#
|
75
|
+
#
|
74
76
|
# @example Create a person
|
75
77
|
# record = client.records.create(
|
76
78
|
# object: 'people',
|
@@ -81,20 +83,20 @@ module Attio
|
|
81
83
|
# }
|
82
84
|
# )
|
83
85
|
def create(object:, data:)
|
84
|
-
|
85
|
-
|
86
|
+
validate_required_string!(object, "Object type")
|
87
|
+
validate_record_data!(data)
|
86
88
|
request(:post, "objects/#{object}/records", data)
|
87
89
|
end
|
88
90
|
|
89
91
|
# Update an existing record.
|
90
|
-
#
|
92
|
+
#
|
91
93
|
# @param object [String] The object type
|
92
94
|
# @param id [String] The record ID to update
|
93
95
|
# @param data [Hash] The updated record data
|
94
|
-
#
|
96
|
+
#
|
95
97
|
# @return [Hash] The updated record data
|
96
98
|
# @raise [ArgumentError] if object, id, or data is invalid
|
97
|
-
#
|
99
|
+
#
|
98
100
|
# @example Update a person's name
|
99
101
|
# record = client.records.update(
|
100
102
|
# object: 'people',
|
@@ -102,57 +104,37 @@ module Attio
|
|
102
104
|
# data: { name: 'Jane Smith' }
|
103
105
|
# )
|
104
106
|
def update(object:, id:, data:)
|
105
|
-
|
106
|
-
validate_id!(id)
|
107
|
-
|
107
|
+
validate_required_string!(object, "Object type")
|
108
|
+
validate_id!(id, "Record")
|
109
|
+
validate_record_data!(data)
|
108
110
|
request(:patch, "objects/#{object}/records/#{id}", data)
|
109
111
|
end
|
110
112
|
|
111
113
|
# Delete a record.
|
112
|
-
#
|
114
|
+
#
|
113
115
|
# @param object [String] The object type
|
114
116
|
# @param id [String] The record ID to delete
|
115
|
-
#
|
117
|
+
#
|
116
118
|
# @return [Hash] Deletion confirmation
|
117
119
|
# @raise [ArgumentError] if object or id is nil or empty
|
118
|
-
#
|
120
|
+
#
|
119
121
|
# @example
|
120
122
|
# client.records.delete(object: 'people', id: 'abc123')
|
121
123
|
def delete(object:, id:)
|
122
|
-
|
123
|
-
validate_id!(id)
|
124
|
+
validate_required_string!(object, "Object type")
|
125
|
+
validate_id!(id, "Record")
|
124
126
|
request(:delete, "objects/#{object}/records/#{id}")
|
125
127
|
end
|
126
128
|
|
127
|
-
private
|
128
|
-
|
129
|
-
# Validates that the object parameter is present and not empty.
|
130
|
-
#
|
131
|
-
# @param object [String, nil] The object type to validate
|
132
|
-
# @raise [ArgumentError] if object is nil or empty
|
133
|
-
# @api private
|
134
|
-
def validate_object!(object)
|
135
|
-
raise ArgumentError, "Object type is required" if object.nil? || object.to_s.strip.empty?
|
136
|
-
end
|
137
|
-
|
138
|
-
# Validates that the ID parameter is present and not empty.
|
139
|
-
#
|
140
|
-
# @param id [String, nil] The record ID to validate
|
141
|
-
# @raise [ArgumentError] if id is nil or empty
|
142
|
-
# @api private
|
143
|
-
def validate_id!(id)
|
144
|
-
raise ArgumentError, "Record ID is required" if id.nil? || id.to_s.strip.empty?
|
145
|
-
end
|
146
|
-
|
147
129
|
# Validates that the data parameter is present and is a hash.
|
148
|
-
#
|
130
|
+
#
|
149
131
|
# @param data [Hash, nil] The data to validate
|
150
132
|
# @raise [ArgumentError] if data is nil or not a hash
|
151
133
|
# @api private
|
152
|
-
def
|
134
|
+
private def validate_record_data!(data)
|
153
135
|
raise ArgumentError, "Data is required" if data.nil?
|
154
136
|
raise ArgumentError, "Data must be a hash" unless data.is_a?(Hash)
|
155
137
|
end
|
156
138
|
end
|
157
139
|
end
|
158
|
-
end
|
140
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attio
|
4
|
+
module Resources
|
5
|
+
# API resource for managing tasks in Attio
|
6
|
+
#
|
7
|
+
# Tasks help track action items and to-dos associated with records.
|
8
|
+
#
|
9
|
+
# @example Creating a task
|
10
|
+
# client.tasks.create(
|
11
|
+
# parent_object: "people",
|
12
|
+
# parent_record_id: "person_123",
|
13
|
+
# title: "Follow up on proposal",
|
14
|
+
# due_date: "2025-02-01",
|
15
|
+
# assignee_id: "user_456"
|
16
|
+
# )
|
17
|
+
#
|
18
|
+
# @example Listing tasks with filters
|
19
|
+
# client.tasks.list(
|
20
|
+
# status: "pending",
|
21
|
+
# assignee_id: "user_456"
|
22
|
+
# )
|
23
|
+
class Tasks < Base
|
24
|
+
# List tasks with optional filters
|
25
|
+
#
|
26
|
+
# @param params [Hash] Query parameters
|
27
|
+
# @option params [String] :parent_object Filter by parent object type
|
28
|
+
# @option params [String] :parent_record_id Filter by parent record
|
29
|
+
# @option params [String] :status Filter by status (pending, completed)
|
30
|
+
# @option params [String] :assignee_id Filter by assignee
|
31
|
+
# @option params [Integer] :limit Number of tasks to return
|
32
|
+
# @option params [String] :cursor Pagination cursor
|
33
|
+
#
|
34
|
+
# @return [Hash] API response containing tasks
|
35
|
+
def list(**params)
|
36
|
+
request(:get, "tasks", params)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get a specific task by ID
|
40
|
+
#
|
41
|
+
# @param id [String] The task ID
|
42
|
+
#
|
43
|
+
# @return [Hash] The task data
|
44
|
+
# @raise [ArgumentError] if id is nil or empty
|
45
|
+
def get(id:)
|
46
|
+
validate_id!(id, "Task")
|
47
|
+
request(:get, "tasks/#{id}")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Create a new task
|
51
|
+
#
|
52
|
+
# @param parent_object [String] The parent object type
|
53
|
+
# @param parent_record_id [String] The ID of the parent record
|
54
|
+
# @param title [String] The task title
|
55
|
+
# @param data [Hash] Additional task data
|
56
|
+
# @option data [String] :description Task description
|
57
|
+
# @option data [String] :due_date Due date (ISO 8601 format)
|
58
|
+
# @option data [String] :assignee_id User ID to assign the task to
|
59
|
+
# @option data [String] :status Task status (pending, completed)
|
60
|
+
# @option data [Integer] :priority Task priority (1-5)
|
61
|
+
#
|
62
|
+
# @return [Hash] The created task
|
63
|
+
# @raise [ArgumentError] if required parameters are missing
|
64
|
+
def create(parent_object:, parent_record_id:, title:, **data)
|
65
|
+
validate_parent!(parent_object, parent_record_id)
|
66
|
+
validate_required_string!(title, "Task title")
|
67
|
+
|
68
|
+
request(:post, "tasks", data.merge(
|
69
|
+
parent_object: parent_object,
|
70
|
+
parent_record_id: parent_record_id,
|
71
|
+
title: title
|
72
|
+
))
|
73
|
+
end
|
74
|
+
|
75
|
+
# Update an existing task
|
76
|
+
#
|
77
|
+
# @param id [String] The task ID
|
78
|
+
# @param data [Hash] The fields to update
|
79
|
+
# @option data [String] :title New title
|
80
|
+
# @option data [String] :description New description
|
81
|
+
# @option data [String] :due_date New due date
|
82
|
+
# @option data [String] :assignee_id New assignee
|
83
|
+
# @option data [String] :status New status
|
84
|
+
# @option data [Integer] :priority New priority
|
85
|
+
#
|
86
|
+
# @return [Hash] The updated task
|
87
|
+
# @raise [ArgumentError] if id is invalid
|
88
|
+
def update(id:, **data)
|
89
|
+
validate_id!(id, "Task")
|
90
|
+
validate_data!(data, "Update")
|
91
|
+
request(:patch, "tasks/#{id}", data)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Mark a task as completed
|
95
|
+
#
|
96
|
+
# @param id [String] The task ID
|
97
|
+
# @param completed_at [String] Optional completion timestamp (defaults to now)
|
98
|
+
#
|
99
|
+
# @return [Hash] The updated task
|
100
|
+
# @raise [ArgumentError] if id is nil or empty
|
101
|
+
def complete(id:, completed_at: nil)
|
102
|
+
validate_id!(id, "Task")
|
103
|
+
data = { status: "completed" }
|
104
|
+
data[:completed_at] = completed_at if completed_at
|
105
|
+
request(:patch, "tasks/#{id}", data)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Reopen a completed task
|
109
|
+
#
|
110
|
+
# @param id [String] The task ID
|
111
|
+
#
|
112
|
+
# @return [Hash] The updated task
|
113
|
+
# @raise [ArgumentError] if id is nil or empty
|
114
|
+
def reopen(id:)
|
115
|
+
validate_id!(id, "Task")
|
116
|
+
request(:patch, "tasks/#{id}", { status: "pending", completed_at: nil })
|
117
|
+
end
|
118
|
+
|
119
|
+
# Delete a task
|
120
|
+
#
|
121
|
+
# @param id [String] The task ID to delete
|
122
|
+
#
|
123
|
+
# @return [Hash] Deletion confirmation
|
124
|
+
# @raise [ArgumentError] if id is nil or empty
|
125
|
+
def delete(id:)
|
126
|
+
validate_id!(id, "Task")
|
127
|
+
request(:delete, "tasks/#{id}")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attio
|
4
|
+
module Resources
|
5
|
+
# API resource for managing threads in Attio
|
6
|
+
#
|
7
|
+
# Threads represent conversations or discussion topics related to records.
|
8
|
+
#
|
9
|
+
# @example Listing threads for a record
|
10
|
+
# client.threads.list(
|
11
|
+
# parent_object: "companies",
|
12
|
+
# parent_record_id: "company_123"
|
13
|
+
# )
|
14
|
+
#
|
15
|
+
# @example Getting a specific thread
|
16
|
+
# client.threads.get(id: "thread_456")
|
17
|
+
class Threads < Base
|
18
|
+
# List threads for a parent record
|
19
|
+
#
|
20
|
+
# @param parent_object [String] The parent object type
|
21
|
+
# @param parent_record_id [String] The parent record ID
|
22
|
+
# @param params [Hash] Additional query parameters
|
23
|
+
# @option params [Integer] :limit Number of threads to return
|
24
|
+
# @option params [String] :cursor Pagination cursor
|
25
|
+
# @option params [String] :status Filter by status (open, closed)
|
26
|
+
#
|
27
|
+
# @return [Hash] API response containing threads
|
28
|
+
# @raise [ArgumentError] if parent_object or parent_record_id is missing
|
29
|
+
def list(parent_object:, parent_record_id:, **params)
|
30
|
+
validate_parent!(parent_object, parent_record_id)
|
31
|
+
request(:get, "threads", params.merge(
|
32
|
+
parent_object: parent_object,
|
33
|
+
parent_record_id: parent_record_id
|
34
|
+
))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get a specific thread by ID
|
38
|
+
#
|
39
|
+
# @param id [String] The thread ID
|
40
|
+
# @param include_comments [Boolean] Whether to include comments in the response
|
41
|
+
#
|
42
|
+
# @return [Hash] The thread data
|
43
|
+
# @raise [ArgumentError] if id is nil or empty
|
44
|
+
def get(id:, include_comments: false)
|
45
|
+
validate_id!(id, "Thread")
|
46
|
+
params = include_comments ? { include: "comments" } : {}
|
47
|
+
request(:get, "threads/#{id}", params)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Create a new thread
|
51
|
+
#
|
52
|
+
# @param parent_object [String] The parent object type
|
53
|
+
# @param parent_record_id [String] The parent record ID
|
54
|
+
# @param title [String] The thread title
|
55
|
+
# @param data [Hash] Additional thread data
|
56
|
+
# @option data [String] :description Thread description
|
57
|
+
# @option data [String] :status Thread status (open, closed)
|
58
|
+
# @option data [Array<String>] :participant_ids User IDs of participants
|
59
|
+
#
|
60
|
+
# @return [Hash] The created thread
|
61
|
+
# @raise [ArgumentError] if required parameters are missing
|
62
|
+
def create(parent_object:, parent_record_id:, title:, **data)
|
63
|
+
validate_parent!(parent_object, parent_record_id)
|
64
|
+
validate_required_string!(title, "Thread title")
|
65
|
+
|
66
|
+
request(:post, "threads", data.merge(
|
67
|
+
parent_object: parent_object,
|
68
|
+
parent_record_id: parent_record_id,
|
69
|
+
title: title
|
70
|
+
))
|
71
|
+
end
|
72
|
+
|
73
|
+
# Update an existing thread
|
74
|
+
#
|
75
|
+
# @param id [String] The thread ID
|
76
|
+
# @param data [Hash] The fields to update
|
77
|
+
# @option data [String] :title New title
|
78
|
+
# @option data [String] :description New description
|
79
|
+
# @option data [String] :status New status (open, closed)
|
80
|
+
#
|
81
|
+
# @return [Hash] The updated thread
|
82
|
+
# @raise [ArgumentError] if id is invalid
|
83
|
+
def update(id:, **data)
|
84
|
+
validate_id!(id, "Thread")
|
85
|
+
validate_data!(data, "Update")
|
86
|
+
request(:patch, "threads/#{id}", data)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Close a thread
|
90
|
+
#
|
91
|
+
# @param id [String] The thread ID
|
92
|
+
#
|
93
|
+
# @return [Hash] The updated thread
|
94
|
+
# @raise [ArgumentError] if id is nil or empty
|
95
|
+
def close(id:)
|
96
|
+
validate_id!(id, "Thread")
|
97
|
+
request(:patch, "threads/#{id}", { status: "closed" })
|
98
|
+
end
|
99
|
+
|
100
|
+
# Reopen a closed thread
|
101
|
+
#
|
102
|
+
# @param id [String] The thread ID
|
103
|
+
#
|
104
|
+
# @return [Hash] The updated thread
|
105
|
+
# @raise [ArgumentError] if id is nil or empty
|
106
|
+
def reopen(id:)
|
107
|
+
validate_id!(id, "Thread")
|
108
|
+
request(:patch, "threads/#{id}", { status: "open" })
|
109
|
+
end
|
110
|
+
|
111
|
+
# Delete a thread
|
112
|
+
#
|
113
|
+
# @param id [String] The thread ID to delete
|
114
|
+
#
|
115
|
+
# @return [Hash] Deletion confirmation
|
116
|
+
# @raise [ArgumentError] if id is nil or empty
|
117
|
+
def delete(id:)
|
118
|
+
validate_id!(id, "Thread")
|
119
|
+
request(:delete, "threads/#{id}")
|
120
|
+
end
|
121
|
+
|
122
|
+
# Add participants to a thread
|
123
|
+
#
|
124
|
+
# @param id [String] The thread ID
|
125
|
+
# @param user_ids [Array<String>] User IDs to add as participants
|
126
|
+
#
|
127
|
+
# @return [Hash] The updated thread
|
128
|
+
# @raise [ArgumentError] if id or user_ids is invalid
|
129
|
+
def add_participants(id:, user_ids:)
|
130
|
+
validate_id!(id, "Thread")
|
131
|
+
validate_user_ids!(user_ids)
|
132
|
+
request(:post, "threads/#{id}/participants", { user_ids: user_ids })
|
133
|
+
end
|
134
|
+
|
135
|
+
# Remove participants from a thread
|
136
|
+
#
|
137
|
+
# @param id [String] The thread ID
|
138
|
+
# @param user_ids [Array<String>] User IDs to remove
|
139
|
+
#
|
140
|
+
# @return [Hash] The updated thread
|
141
|
+
# @raise [ArgumentError] if id or user_ids is invalid
|
142
|
+
def remove_participants(id:, user_ids:)
|
143
|
+
validate_id!(id, "Thread")
|
144
|
+
validate_user_ids!(user_ids)
|
145
|
+
request(:delete, "threads/#{id}/participants", { user_ids: user_ids })
|
146
|
+
end
|
147
|
+
|
148
|
+
private def validate_user_ids!(user_ids)
|
149
|
+
raise ArgumentError, "User IDs are required" if user_ids.nil? || user_ids.empty?
|
150
|
+
raise ArgumentError, "User IDs must be an array" unless user_ids.is_a?(Array)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -1,5 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attio
|
2
4
|
module Resources
|
5
|
+
# API resource for managing workspace users
|
6
|
+
#
|
7
|
+
# Users are people who have access to your Attio workspace.
|
8
|
+
#
|
9
|
+
# @example Listing all users
|
10
|
+
# client.users.list
|
3
11
|
class Users < Base
|
4
12
|
def list(**params)
|
5
13
|
request(:get, "users", params)
|
@@ -10,11 +18,9 @@ module Attio
|
|
10
18
|
request(:get, "users/#{id}")
|
11
19
|
end
|
12
20
|
|
13
|
-
private
|
14
|
-
|
15
|
-
def validate_id!(id)
|
21
|
+
private def validate_id!(id)
|
16
22
|
raise ArgumentError, "User ID is required" if id.nil? || id.to_s.strip.empty?
|
17
23
|
end
|
18
24
|
end
|
19
25
|
end
|
20
|
-
end
|
26
|
+
end
|
@@ -1,5 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attio
|
2
4
|
module Resources
|
5
|
+
# API resource for managing workspace information
|
6
|
+
#
|
7
|
+
# Workspaces are the top-level organizational unit in Attio.
|
8
|
+
#
|
9
|
+
# @example Getting workspace information
|
10
|
+
# client.workspaces.get
|
3
11
|
class Workspaces < Base
|
4
12
|
def get
|
5
13
|
request(:get, "workspace")
|
@@ -10,4 +18,4 @@ module Attio
|
|
10
18
|
end
|
11
19
|
end
|
12
20
|
end
|
13
|
-
end
|
21
|
+
end
|
data/lib/attio/retry_handler.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attio
|
4
|
+
# Handles automatic retry logic for failed API requests
|
5
|
+
#
|
6
|
+
# This class implements exponential backoff retry strategy for
|
7
|
+
# transient errors like timeouts and rate limits.
|
8
|
+
#
|
9
|
+
# @example Using the retry handler
|
10
|
+
# handler = RetryHandler.new(max_retries: 5)
|
11
|
+
# handler.with_retry { api_client.get("/endpoint") }
|
2
12
|
class RetryHandler
|
3
13
|
DEFAULT_MAX_RETRIES = 3
|
4
14
|
DEFAULT_RETRY_DELAY = 1 # seconds
|
@@ -7,12 +17,12 @@ module Attio
|
|
7
17
|
HttpClient::TimeoutError,
|
8
18
|
HttpClient::ConnectionError,
|
9
19
|
ServerError,
|
10
|
-
RateLimitError
|
20
|
+
RateLimitError,
|
11
21
|
].freeze
|
12
22
|
|
13
23
|
attr_reader :max_retries, :retry_delay, :backoff_factor, :logger
|
14
24
|
|
15
|
-
def initialize(max_retries: DEFAULT_MAX_RETRIES,
|
25
|
+
def initialize(max_retries: DEFAULT_MAX_RETRIES,
|
16
26
|
retry_delay: DEFAULT_RETRY_DELAY,
|
17
27
|
backoff_factor: DEFAULT_BACKOFF_FACTOR,
|
18
28
|
logger: nil)
|
@@ -22,7 +32,7 @@ module Attio
|
|
22
32
|
@logger = logger
|
23
33
|
end
|
24
34
|
|
25
|
-
def with_retry
|
35
|
+
def with_retry
|
26
36
|
retries = 0
|
27
37
|
delay = retry_delay
|
28
38
|
|
@@ -30,7 +40,7 @@ module Attio
|
|
30
40
|
yield
|
31
41
|
rescue *RETRIABLE_ERRORS => e
|
32
42
|
retries += 1
|
33
|
-
|
43
|
+
|
34
44
|
if retries <= max_retries
|
35
45
|
log_retry(e, retries, delay)
|
36
46
|
sleep(delay)
|
@@ -43,11 +53,9 @@ module Attio
|
|
43
53
|
end
|
44
54
|
end
|
45
55
|
|
46
|
-
private
|
47
|
-
|
48
|
-
def log_retry(error, attempt, delay)
|
56
|
+
private def log_retry(error, attempt, delay)
|
49
57
|
return unless logger
|
50
|
-
|
58
|
+
|
51
59
|
logger.warn(
|
52
60
|
"Retry attempt #{attempt}/#{max_retries}",
|
53
61
|
error: error.class.name,
|
@@ -56,9 +64,9 @@ module Attio
|
|
56
64
|
)
|
57
65
|
end
|
58
66
|
|
59
|
-
def log_failure(error, attempts)
|
67
|
+
private def log_failure(error, attempts)
|
60
68
|
return unless logger
|
61
|
-
|
69
|
+
|
62
70
|
logger.error(
|
63
71
|
"Max retries exceeded",
|
64
72
|
error: error.class.name,
|
@@ -67,4 +75,4 @@ module Attio
|
|
67
75
|
)
|
68
76
|
end
|
69
77
|
end
|
70
|
-
end
|
78
|
+
end
|
data/lib/attio/version.rb
CHANGED
data/lib/attio.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "typhoeus"
|
2
4
|
|
3
5
|
require "attio/version"
|
@@ -12,34 +14,38 @@ require "attio/resources/lists"
|
|
12
14
|
require "attio/resources/workspaces"
|
13
15
|
require "attio/resources/attributes"
|
14
16
|
require "attio/resources/users"
|
17
|
+
require "attio/resources/notes"
|
18
|
+
require "attio/resources/tasks"
|
19
|
+
require "attio/resources/comments"
|
20
|
+
require "attio/resources/threads"
|
15
21
|
|
16
22
|
# The main Attio module provides access to the Attio API client.
|
17
|
-
#
|
23
|
+
#
|
18
24
|
# This is the primary entry point for interacting with the Attio API.
|
19
|
-
#
|
25
|
+
#
|
20
26
|
# @example Basic usage
|
21
27
|
# client = Attio.client(api_key: 'your-api-key')
|
22
|
-
#
|
28
|
+
#
|
23
29
|
# @example Working with records
|
24
30
|
# # List records for a specific object type
|
25
31
|
# records = client.records.list(object: 'people', filters: { name: 'John' })
|
26
|
-
#
|
32
|
+
#
|
27
33
|
# # Create a new record
|
28
34
|
# new_record = client.records.create(
|
29
35
|
# object: 'people',
|
30
36
|
# data: { name: 'Jane Doe', email: 'jane@example.com' }
|
31
37
|
# )
|
32
|
-
#
|
38
|
+
#
|
33
39
|
# # Get a specific record
|
34
40
|
# record = client.records.get(object: 'people', id: 'record-id')
|
35
|
-
#
|
41
|
+
#
|
36
42
|
# # Update a record
|
37
43
|
# updated = client.records.update(
|
38
44
|
# object: 'people',
|
39
45
|
# id: 'record-id',
|
40
46
|
# data: { name: 'Jane Smith' }
|
41
47
|
# )
|
42
|
-
#
|
48
|
+
#
|
43
49
|
# # Delete a record
|
44
50
|
# client.records.delete(object: 'people', id: 'record-id')
|
45
51
|
#
|
@@ -47,11 +53,11 @@ require "attio/resources/users"
|
|
47
53
|
# @since 1.0.0
|
48
54
|
module Attio
|
49
55
|
# Creates a new Attio API client instance.
|
50
|
-
#
|
56
|
+
#
|
51
57
|
# @param api_key [String] Your Attio API key
|
52
58
|
# @return [Client] A new client instance configured with the provided API key
|
53
59
|
# @raise [ArgumentError] if api_key is nil or empty
|
54
|
-
#
|
60
|
+
#
|
55
61
|
# @example Create a client
|
56
62
|
# client = Attio.client(api_key: 'your-api-key-here')
|
57
63
|
def self.client(api_key:)
|