attio 0.3.0 → 0.5.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/.gitignore +2 -0
- data/CHANGELOG.md +83 -2
- data/CLAUDE.md +35 -4
- data/Gemfile.lock +1 -1
- data/META_IMPLEMENTATION_PLAN.md +205 -0
- data/README.md +361 -37
- data/lib/attio/circuit_breaker.rb +299 -0
- data/lib/attio/client.rb +11 -10
- data/lib/attio/connection_pool.rb +190 -35
- data/lib/attio/enhanced_client.rb +257 -0
- data/lib/attio/http_client.rb +54 -3
- data/lib/attio/observability.rb +424 -0
- data/lib/attio/resources/attributes.rb +244 -0
- data/lib/attio/resources/base.rb +53 -0
- data/lib/attio/resources/bulk.rb +1 -1
- data/lib/attio/resources/lists.rb +195 -0
- data/lib/attio/resources/meta.rb +103 -42
- data/lib/attio/resources/objects.rb +104 -0
- data/lib/attio/resources/records.rb +97 -2
- data/lib/attio/resources/workspaces.rb +11 -2
- data/lib/attio/version.rb +1 -1
- data/lib/attio/webhooks.rb +220 -0
- data/lib/attio.rb +9 -1
- metadata +6 -1
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Attio Ruby Client
|
2
2
|
|
3
3
|
[](https://github.com/idl3/attio/actions/workflows/tests.yml)
|
4
|
-
[](https://github.com/idl3/attio/tree/master/spec)
|
5
5
|
[](https://idl3.github.io/attio)
|
6
6
|
[](https://badge.fury.io/rb/attio)
|
7
|
-
[](https://github.com/idl3/attio/tree/master/spec)
|
8
8
|
|
9
9
|
Ruby client for the [Attio CRM API](https://developers.attio.com/). This library provides easy access to the Attio API, allowing you to manage records, objects, lists, and more.
|
10
10
|
|
@@ -84,22 +84,21 @@ client = Attio::Client.new(api_key: 'your-api-key', timeout: 60)
|
|
84
84
|
# List all people
|
85
85
|
people = client.records.list(object: 'people')
|
86
86
|
|
87
|
-
# List with
|
87
|
+
# List with filtering and sorting
|
88
88
|
filtered_people = client.records.list(
|
89
89
|
object: 'people',
|
90
|
-
|
91
|
-
name: { contains: 'John' }
|
92
|
-
company: { target_object: 'companies', target_record_id: 'company-123' }
|
90
|
+
filter: {
|
91
|
+
name: { $contains: 'John' }
|
93
92
|
},
|
94
|
-
|
93
|
+
sort: 'created_at.desc',
|
94
|
+
limit: 50,
|
95
|
+
offset: 0
|
95
96
|
)
|
96
97
|
|
97
|
-
# List with
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
limit: 25
|
102
|
-
)
|
98
|
+
# List all records with automatic pagination
|
99
|
+
client.records.list_all(object: 'people', page_size: 50).each do |person|
|
100
|
+
puts person['name']
|
101
|
+
end
|
103
102
|
```
|
104
103
|
|
105
104
|
#### Creating Records
|
@@ -304,14 +303,39 @@ client.notes.update(
|
|
304
303
|
client.notes.delete(id: 'note-123')
|
305
304
|
```
|
306
305
|
|
307
|
-
#### Objects
|
306
|
+
#### Objects (including Custom Objects)
|
308
307
|
|
309
308
|
```ruby
|
310
309
|
# List all object types
|
311
310
|
objects = client.objects.list
|
312
311
|
|
313
312
|
# Get a specific object schema
|
314
|
-
people_object = client.objects.get(
|
313
|
+
people_object = client.objects.get(id_or_slug: 'people')
|
314
|
+
|
315
|
+
# Create a custom object
|
316
|
+
custom_object = client.objects.create(
|
317
|
+
api_slug: 'projects',
|
318
|
+
singular_noun: 'Project',
|
319
|
+
plural_noun: 'Projects'
|
320
|
+
)
|
321
|
+
|
322
|
+
# Update a custom object
|
323
|
+
client.objects.update(
|
324
|
+
id_or_slug: 'projects',
|
325
|
+
plural_noun: 'Active Projects'
|
326
|
+
)
|
327
|
+
|
328
|
+
# Update multiple fields
|
329
|
+
client.objects.update(
|
330
|
+
id_or_slug: 'projects',
|
331
|
+
api_slug: 'active_projects',
|
332
|
+
singular_noun: 'Active Project',
|
333
|
+
plural_noun: 'Active Projects'
|
334
|
+
)
|
335
|
+
|
336
|
+
# NOTE: The Attio API v2.0.0 does not currently support deleting custom objects
|
337
|
+
# Calling delete/destroy will raise NotImplementedError with instructions
|
338
|
+
# To delete objects, use: Settings > Data Model > Objects in the Attio UI
|
315
339
|
```
|
316
340
|
|
317
341
|
#### Lists
|
@@ -357,8 +381,34 @@ attribute = client.attributes.create(
|
|
357
381
|
# List workspace users
|
358
382
|
users = client.users.list
|
359
383
|
|
360
|
-
# Get
|
361
|
-
user = client.users.
|
384
|
+
# Get a specific user by ID
|
385
|
+
user = client.users.get(id: 'user-123')
|
386
|
+
```
|
387
|
+
|
388
|
+
#### Meta (Token & Workspace Info)
|
389
|
+
|
390
|
+
```ruby
|
391
|
+
# Get current token and workspace information
|
392
|
+
meta = client.meta.identify
|
393
|
+
# => { "data" => { "active" => true, "workspace_name" => "My Workspace", ... } }
|
394
|
+
|
395
|
+
# Check if token is active
|
396
|
+
if client.meta.active?
|
397
|
+
puts "Token is valid and active"
|
398
|
+
end
|
399
|
+
|
400
|
+
# Get workspace details
|
401
|
+
workspace = client.meta.workspace
|
402
|
+
# => { "id" => "...", "name" => "My Workspace", "slug" => "my-workspace" }
|
403
|
+
|
404
|
+
# Check permissions
|
405
|
+
if client.meta.permission?("record_permission:read-write")
|
406
|
+
puts "Can read and write records"
|
407
|
+
end
|
408
|
+
|
409
|
+
# Get all permissions
|
410
|
+
permissions = client.meta.permissions
|
411
|
+
# => ["comment:read-write", "list_configuration:read", ...]
|
362
412
|
```
|
363
413
|
|
364
414
|
### Advanced Features
|
@@ -473,23 +523,184 @@ status = limiter.status
|
|
473
523
|
puts "Remaining: #{status[:remaining]}/#{status[:limit]}"
|
474
524
|
```
|
475
525
|
|
476
|
-
|
526
|
+
## Enterprise Features
|
527
|
+
|
528
|
+
The gem includes advanced enterprise features for production use:
|
529
|
+
|
530
|
+
### Enhanced Client
|
531
|
+
|
532
|
+
The `EnhancedClient` provides production-ready features including connection pooling, circuit breaker, observability, and webhook support:
|
533
|
+
|
534
|
+
```ruby
|
535
|
+
# Create an enhanced client with all features
|
536
|
+
client = Attio.enhanced_client(
|
537
|
+
api_key: ENV['ATTIO_API_KEY'],
|
538
|
+
connection_pool: {
|
539
|
+
size: 10, # Pool size
|
540
|
+
timeout: 5 # Checkout timeout
|
541
|
+
},
|
542
|
+
circuit_breaker: {
|
543
|
+
threshold: 5, # Failures before opening
|
544
|
+
timeout: 30, # Recovery timeout in seconds
|
545
|
+
half_open_requests: 2
|
546
|
+
},
|
547
|
+
instrumentation: {
|
548
|
+
logger: Rails.logger,
|
549
|
+
metrics: :datadog, # or :statsd, :prometheus, :opentelemetry
|
550
|
+
traces: :datadog # or :opentelemetry
|
551
|
+
},
|
552
|
+
webhook_secret: ENV['ATTIO_WEBHOOK_SECRET']
|
553
|
+
)
|
554
|
+
|
555
|
+
# Use it like a regular client
|
556
|
+
records = client.records.list(object: 'people')
|
557
|
+
|
558
|
+
# Execute with circuit breaker protection
|
559
|
+
client.execute(endpoint: 'api/records') do
|
560
|
+
client.records.create(object: 'people', data: { name: 'John' })
|
561
|
+
end
|
562
|
+
|
563
|
+
# Check health of all components
|
564
|
+
health = client.health_check
|
565
|
+
# => { api: true, pool: true, circuit_breaker: :healthy, rate_limiter: true }
|
566
|
+
|
567
|
+
# Verify API connectivity and token validity
|
568
|
+
if client.meta.active?
|
569
|
+
puts "API connection healthy, token valid"
|
570
|
+
end
|
571
|
+
|
572
|
+
# Get statistics
|
573
|
+
stats = client.stats
|
574
|
+
# => { pool: { size: 10, available: 7 }, circuit_breaker: { state: :closed, requests: 100 } }
|
575
|
+
```
|
576
|
+
|
577
|
+
### Connection Pooling
|
578
|
+
|
579
|
+
Efficient connection management with thread-safe pooling:
|
580
|
+
|
581
|
+
```ruby
|
582
|
+
pool = Attio::ConnectionPool.new(size: 5, timeout: 2) do
|
583
|
+
Attio::HttpClient.new(
|
584
|
+
base_url: 'https://api.attio.com/v2',
|
585
|
+
headers: { 'Authorization' => "Bearer #{api_key}" }
|
586
|
+
)
|
587
|
+
end
|
588
|
+
|
589
|
+
# Use connections from the pool
|
590
|
+
pool.with do |connection|
|
591
|
+
connection.get('records')
|
592
|
+
end
|
593
|
+
|
594
|
+
# Check pool status
|
595
|
+
stats = pool.stats
|
596
|
+
# => { size: 5, available: 3, allocated: 2 }
|
597
|
+
|
598
|
+
# Graceful shutdown
|
599
|
+
pool.shutdown
|
600
|
+
```
|
601
|
+
|
602
|
+
### Circuit Breaker
|
603
|
+
|
604
|
+
Fault tolerance with circuit breaker pattern:
|
605
|
+
|
606
|
+
```ruby
|
607
|
+
breaker = Attio::CircuitBreaker.new(
|
608
|
+
threshold: 5, # Open after 5 failures
|
609
|
+
timeout: 30, # Reset after 30 seconds
|
610
|
+
half_open_requests: 2
|
611
|
+
)
|
612
|
+
|
613
|
+
# Execute with protection
|
614
|
+
result = breaker.execute do
|
615
|
+
risky_api_call
|
616
|
+
end
|
617
|
+
|
618
|
+
# Monitor state changes
|
619
|
+
breaker.on_state_change = ->(old_state, new_state) {
|
620
|
+
puts "Circuit breaker: #{old_state} -> #{new_state}"
|
621
|
+
}
|
622
|
+
|
623
|
+
# Check current state
|
624
|
+
breaker.state # => :closed, :open, or :half_open
|
625
|
+
breaker.stats # => { requests: 100, failures: 2, success_rate: 0.98 }
|
626
|
+
```
|
627
|
+
|
628
|
+
### Observability
|
629
|
+
|
630
|
+
Comprehensive monitoring with multiple backend support:
|
631
|
+
|
632
|
+
```ruby
|
633
|
+
# Initialize with your preferred backend
|
634
|
+
instrumentation = Attio::Observability::Instrumentation.new(
|
635
|
+
logger: Logger.new(STDOUT),
|
636
|
+
metrics_backend: :datadog, # :statsd, :prometheus, :memory
|
637
|
+
trace_backend: :opentelemetry # :datadog, :memory
|
638
|
+
)
|
639
|
+
|
640
|
+
# Record API calls
|
641
|
+
instrumentation.record_api_call(
|
642
|
+
method: :post,
|
643
|
+
path: '/records',
|
644
|
+
duration: 0.125,
|
645
|
+
status: 200
|
646
|
+
)
|
647
|
+
|
648
|
+
# Record rate limits
|
649
|
+
instrumentation.record_rate_limit(
|
650
|
+
remaining: 450,
|
651
|
+
limit: 500,
|
652
|
+
reset_at: Time.now + 3600
|
653
|
+
)
|
654
|
+
|
655
|
+
# Record circuit breaker state changes
|
656
|
+
instrumentation.record_circuit_breaker(
|
657
|
+
endpoint: 'api/records',
|
658
|
+
old_state: :closed,
|
659
|
+
new_state: :open
|
660
|
+
)
|
661
|
+
|
662
|
+
# Track pool statistics
|
663
|
+
instrumentation.record_pool_stats(
|
664
|
+
size: 10,
|
665
|
+
available: 7,
|
666
|
+
allocated: 3
|
667
|
+
)
|
668
|
+
```
|
669
|
+
|
670
|
+
### Webhook Processing
|
671
|
+
|
672
|
+
Secure webhook handling with signature verification:
|
477
673
|
|
478
674
|
```ruby
|
479
|
-
#
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
675
|
+
# Initialize webhook handler
|
676
|
+
webhooks = Attio::Webhooks.new(secret: ENV['ATTIO_WEBHOOK_SECRET'])
|
677
|
+
|
678
|
+
# Register event handlers
|
679
|
+
webhooks.on('record.created') do |event|
|
680
|
+
puts "New record: #{event.data['id']}"
|
681
|
+
end
|
682
|
+
|
683
|
+
webhooks.on_any do |event|
|
684
|
+
puts "Event: #{event.type}"
|
685
|
+
end
|
686
|
+
|
687
|
+
# Process incoming webhook
|
688
|
+
begin
|
689
|
+
event = webhooks.process(
|
690
|
+
request.body.read,
|
691
|
+
request.headers
|
692
|
+
)
|
693
|
+
render json: { status: 'ok' }
|
694
|
+
rescue Attio::Webhooks::InvalidSignatureError => e
|
695
|
+
render json: { error: 'Invalid signature' }, status: 401
|
696
|
+
end
|
697
|
+
|
698
|
+
# Development webhook server
|
699
|
+
server = Attio::WebhookServer.new(port: 3001, secret: 'test_secret')
|
700
|
+
server.webhooks.on('record.created') do |event|
|
701
|
+
puts "Received: #{event.inspect}"
|
702
|
+
end
|
703
|
+
server.start # Starts WEBrick server for testing
|
493
704
|
```
|
494
705
|
|
495
706
|
### Error Handling
|
@@ -523,11 +734,12 @@ This client supports all major Attio API endpoints:
|
|
523
734
|
|
524
735
|
### Core Resources
|
525
736
|
- ✅ **Records** - Full CRUD operations, querying with filters and sorting
|
526
|
-
- ✅ **Objects** - List, get
|
737
|
+
- ✅ **Objects** - List, get, create and update custom objects
|
527
738
|
- ✅ **Lists** - List, get entries, manage list entries
|
528
739
|
- ✅ **Attributes** - List, create, update custom attributes
|
529
740
|
- ✅ **Workspaces** - List, get current workspace
|
530
|
-
- ✅ **Users** - List, get
|
741
|
+
- ✅ **Users** - List, get specific user
|
742
|
+
- ✅ **Meta** - Get token info, workspace details, and permissions (/v2/self endpoint)
|
531
743
|
|
532
744
|
### Collaboration Features
|
533
745
|
- ✅ **Comments** - CRUD operations, emoji reactions on records and threads
|
@@ -540,9 +752,16 @@ This client supports all major Attio API endpoints:
|
|
540
752
|
- ✅ **Workspace Members** - Member management, invitations, permissions
|
541
753
|
|
542
754
|
### Advanced Features
|
543
|
-
- ✅ **Bulk Operations** - Batch create/update/delete with automatic batching
|
755
|
+
- ✅ **Bulk Operations** - Batch create/update/delete with automatic batching (1000 items max)
|
544
756
|
- ✅ **Rate Limiting** - Intelligent retry with exponential backoff and request queuing
|
545
|
-
|
757
|
+
|
758
|
+
### Enterprise Features
|
759
|
+
- ✅ **Enhanced Client** - Production-ready client with pooling, circuit breaker, and observability
|
760
|
+
- ✅ **Connection Pooling** - Thread-safe connection management with configurable pool size
|
761
|
+
- ✅ **Circuit Breaker** - Fault tolerance with automatic recovery and state monitoring
|
762
|
+
- ✅ **Observability** - Metrics and tracing with StatsD, Datadog, Prometheus, OpenTelemetry support
|
763
|
+
- ✅ **Webhook Processing** - Secure webhook handling with HMAC signature verification
|
764
|
+
- ✅ **Middleware** - Request/response instrumentation for monitoring
|
546
765
|
|
547
766
|
## Development
|
548
767
|
|
@@ -569,10 +788,115 @@ bundle exec rake docs:serve
|
|
569
788
|
|
570
789
|
### Code Coverage
|
571
790
|
|
791
|
+
The gem maintains 100% test coverage across all features:
|
792
|
+
|
572
793
|
```bash
|
573
|
-
|
794
|
+
# Run tests with coverage report
|
795
|
+
bundle exec rspec
|
796
|
+
|
797
|
+
# View detailed coverage report
|
798
|
+
open coverage/index.html
|
574
799
|
```
|
575
800
|
|
801
|
+
Current stats:
|
802
|
+
- **Test Coverage**: 99.86% (1392/1394 lines)
|
803
|
+
- **Test Count**: 658 tests
|
804
|
+
- **RuboCop**: 0 violations
|
805
|
+
|
806
|
+
|
807
|
+
## Pending API Functionalities
|
808
|
+
|
809
|
+
The following Attio API endpoints are not yet implemented in this gem. Contributions are welcome!
|
810
|
+
|
811
|
+
### 🔴 Critical Missing Endpoints
|
812
|
+
|
813
|
+
#### Records API
|
814
|
+
- **Assert Record** (`PUT /v2/objects/{object}/records`) - Upsert functionality using matching attributes
|
815
|
+
- **Update with PUT** (`PUT /v2/objects/{object}/records/{record_id}`) - Overwrites multiselect values (PATCH appends)
|
816
|
+
|
817
|
+
#### Lists API
|
818
|
+
- **Create List** (`POST /v2/lists`) - Create new lists programmatically
|
819
|
+
- **Update List** (`PATCH /v2/lists/{list}`) - Modify list configuration
|
820
|
+
- **Query Entries** (`POST /v2/lists/{list}/entries/query`) - Advanced filtering and sorting
|
821
|
+
- **Assert Entry** (`PUT /v2/lists/{list}/entries`) - Upsert list entries
|
822
|
+
- **Update Entry** (`PATCH /v2/lists/{list}/entries/{entry}`) - Modify list entries
|
823
|
+
|
824
|
+
#### Attributes API
|
825
|
+
- **Update Attribute** (`PATCH /v2/{target}/{identifier}/attributes/{attribute}`) - Modify attribute properties
|
826
|
+
- **Select Options Management:**
|
827
|
+
- List Options (`GET /v2/{target}/{identifier}/attributes/{attribute}/options`)
|
828
|
+
- Create Option (`POST /v2/{target}/{identifier}/attributes/{attribute}/options`)
|
829
|
+
- Update Option (`PATCH /v2/{target}/{identifier}/attributes/{attribute}/options/{option}`)
|
830
|
+
- **Status Management:**
|
831
|
+
- List Statuses (`GET /v2/{target}/{identifier}/attributes/{attribute}/statuses`)
|
832
|
+
- Create Status (`POST /v2/{target}/{identifier}/attributes/{attribute}/statuses`)
|
833
|
+
- Update Status (`PATCH /v2/{target}/{identifier}/attributes/{attribute}/statuses/{status}`)
|
834
|
+
|
835
|
+
#### Webhook Management API (Entire Resource Missing)
|
836
|
+
- **List Webhooks** (`GET /v2/webhooks`)
|
837
|
+
- **Create Webhook** (`POST /v2/webhooks`)
|
838
|
+
- **Get Webhook** (`GET /v2/webhooks/{webhook_id}`)
|
839
|
+
- **Update Webhook** (`PATCH /v2/webhooks/{webhook_id}`)
|
840
|
+
- **Delete Webhook** (`DELETE /v2/webhooks/{webhook_id}`)
|
841
|
+
|
842
|
+
### 🟡 Advanced Features Not Implemented
|
843
|
+
|
844
|
+
#### Values API (Entire Resource Missing)
|
845
|
+
- Historic value tracking
|
846
|
+
- Value validation endpoints
|
847
|
+
- Format conversion utilities
|
848
|
+
- Computed values
|
849
|
+
|
850
|
+
#### Import/Export API
|
851
|
+
- Bulk data import endpoints
|
852
|
+
- Export jobs management
|
853
|
+
- Import mapping configuration
|
854
|
+
|
855
|
+
#### Analytics & Reporting
|
856
|
+
- Aggregation queries
|
857
|
+
- Report generation
|
858
|
+
- Dashboard metrics
|
859
|
+
- Activity analytics
|
860
|
+
|
861
|
+
#### Advanced Search
|
862
|
+
- Cross-object search
|
863
|
+
- Full-text search capabilities
|
864
|
+
- Saved search management
|
865
|
+
|
866
|
+
### 🟢 API Limitations (Not Supported by Attio)
|
867
|
+
|
868
|
+
These operations are not available via the API and must be done through the Attio UI:
|
869
|
+
- **Delete Custom Objects** - Must use Settings > Data Model > Objects
|
870
|
+
- **Delete Attributes** - Not supported via API
|
871
|
+
- **Webhook Configuration** (in some cases) - UI configuration required
|
872
|
+
|
873
|
+
### Implementation Status by Category
|
874
|
+
|
875
|
+
| Category | Coverage | Status |
|
876
|
+
|----------|----------|--------|
|
877
|
+
| **Records** | 85% | Missing assert/PUT operations |
|
878
|
+
| **Objects** | 100% | Complete (API limitations noted) |
|
879
|
+
| **Lists** | 60% | Missing create/update/query operations |
|
880
|
+
| **Attributes** | 30% | Missing update and options management |
|
881
|
+
| **Comments** | 100%+ | Over-implemented vs. documented API |
|
882
|
+
| **Threads** | 100%+ | Over-implemented vs. documented API |
|
883
|
+
| **Tasks** | 100% | Complete |
|
884
|
+
| **Notes** | 100%+ | Over-implemented vs. documented API |
|
885
|
+
| **Webhooks** | 50% | Event handling only, missing management |
|
886
|
+
| **Users** | 100% | Complete |
|
887
|
+
| **Workspace Members** | 100% | Complete |
|
888
|
+
| **Meta/Self** | 100% | Complete |
|
889
|
+
| **Values** | 0% | Not implemented |
|
890
|
+
| **Analytics** | 0% | Not implemented |
|
891
|
+
| **Import/Export** | 0% | Not implemented |
|
892
|
+
|
893
|
+
### Notes for Contributors
|
894
|
+
|
895
|
+
1. **Authentication**: Some endpoints require specific scopes (e.g., `object_configuration:read-write`)
|
896
|
+
2. **Rate Limiting**: The gem includes rate limiting support for all new endpoints
|
897
|
+
3. **Testing**: Please add comprehensive tests for any new endpoints
|
898
|
+
4. **Documentation**: Update both inline YARD docs and README examples
|
899
|
+
|
576
900
|
## Contributing
|
577
901
|
|
578
902
|
Bug reports and pull requests are welcome on GitHub at https://github.com/idl3/attio.
|