Class: Attio::EnhancedClient Private

Inherits:
Client
  • Object
show all
Defined in:
lib/attio/enhanced_client.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Enhanced client with enterprise features

Examples:

With connection pooling

client = Attio::EnhancedClient.new(
  api_key: 'your-api-key',
  connection_pool: { size: 10, timeout: 5 },
  circuit_breaker: { threshold: 5, timeout: 60 },
  instrumentation: { logger: Rails.logger, metrics: :datadog }
)

Since:

  • 1.0.0

Constant Summary collapse

CIRCUIT_STATES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0

{
  closed: :healthy,
  half_open: :recovering,
  open: :unhealthy,
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, timeout: DEFAULT_TIMEOUT, connection_pool: nil, circuit_breaker: nil, instrumentation: nil, webhook_secret: nil) ⇒ EnhancedClient

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Initialize enhanced client with enterprise features

Since:

  • 1.0.0



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/attio/enhanced_client.rb', line 30

def initialize(
  api_key:,
  timeout: DEFAULT_TIMEOUT,
  connection_pool: nil,
  circuit_breaker: nil,
  instrumentation: nil,
  webhook_secret: nil
)
  super(api_key: api_key, timeout: timeout)

  setup_connection_pool(connection_pool) if connection_pool
  setup_circuit_breaker(circuit_breaker) if circuit_breaker
  setup_instrumentation(instrumentation) if instrumentation
  setup_webhooks(webhook_secret) if webhook_secret
end

Instance Attribute Details

#circuit_breakerObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



20
21
22
# File 'lib/attio/enhanced_client.rb', line 20

def circuit_breaker
  @circuit_breaker
end

#instrumentationObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



20
21
22
# File 'lib/attio/enhanced_client.rb', line 20

def instrumentation
  @instrumentation
end

#poolObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



20
21
22
# File 'lib/attio/enhanced_client.rb', line 20

def pool
  @pool
end

#webhooksObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



20
21
22
# File 'lib/attio/enhanced_client.rb', line 20

def webhooks
  @webhooks
end

Instance Method Details

#check_api_healthObject (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



225
226
227
228
229
230
# File 'lib/attio/enhanced_client.rb', line 225

private def check_api_health
  connection.get("self")
  true
rescue StandardError
  false
end

#circuit_breaker_healthObject (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



232
233
234
235
236
# File 'lib/attio/enhanced_client.rb', line 232

private def circuit_breaker_health
  return true unless @circuit_breaker

  CIRCUIT_STATES[@circuit_breaker.state]
end

#connectionObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Override connection to use pooled connections

Since:

  • 1.0.0



47
48
49
50
51
52
53
54
55
56
# File 'lib/attio/enhanced_client.rb', line 47

def connection
  return super unless @pool

  @connection ||= begin
    client = PooledHttpClient.new(@pool)
    client = wrap_with_circuit_breaker(client) if @circuit_breaker
    client = wrap_with_instrumentation(client) if @instrumentation
    client
  end
end

#default_headersObject (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



216
217
218
219
220
221
222
223
# File 'lib/attio/enhanced_client.rb', line 216

private def default_headers
  {
    "Authorization" => "Bearer #{api_key}",
    "Accept" => "application/json",
    "Content-Type" => "application/json",
    "User-Agent" => "Attio Ruby Client/#{VERSION}",
  }
end

#execute(endpoint: nil) { ... } ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Execute with automatic retries and circuit breaking

Yields:

  • Block to execute

Since:

  • 1.0.0



62
63
64
65
66
67
68
69
70
71
# File 'lib/attio/enhanced_client.rb', line 62

def execute(endpoint: nil, &block)
  if @circuit_breaker && endpoint
    @composite_breaker ||= CompositeCircuitBreaker.new
    @composite_breaker.call(endpoint, &block)
  elsif @circuit_breaker
    @circuit_breaker.call(&block)
  else
    yield
  end
end

#health_checkHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Health check for all components

Since:

  • 1.0.0



76
77
78
79
80
81
82
83
# File 'lib/attio/enhanced_client.rb', line 76

def health_check
  {
    api: check_api_health,
    pool: @pool&.healthy? || true,
    circuit_breaker: circuit_breaker_health,
    rate_limiter: rate_limiter.status[:remaining] > 0,
  }
end

#setup_circuit_breaker(config) ⇒ Object (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/attio/enhanced_client.rb', line 123

private def setup_circuit_breaker(config)
  @circuit_breaker = CircuitBreaker.new(
    threshold: config[:threshold] || 5,
    timeout: config[:timeout] || 60,
    half_open_requests: config[:half_open_requests] || 3,
    exceptions: [Attio::Error, Timeout::Error, Errno::ECONNREFUSED]
  )

  # Set up state change notifications
  @circuit_breaker.on_state_change = lambda do |old_state, new_state|
    @instrumentation&.record_circuit_breaker(
      endpoint: "api",
      old_state: old_state,
      new_state: new_state
    )
  end
end

#setup_connection_pool(config) ⇒ Object (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/attio/enhanced_client.rb', line 109

private def setup_connection_pool(config)
  pool_size = config[:size] || ConnectionPool::DEFAULT_SIZE
  pool_timeout = config[:timeout] || ConnectionPool::DEFAULT_TIMEOUT

  @pool = ConnectionPool.new(size: pool_size, timeout: pool_timeout) do
    HttpClient.new(
      base_url: API_BASE_URL,
      headers: default_headers,
      timeout: timeout,
      rate_limiter: rate_limiter
    )
  end
end

#setup_instrumentation(config) ⇒ Object (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/attio/enhanced_client.rb', line 141

private def setup_instrumentation(config)
  @instrumentation = Observability::Instrumentation.new(
    logger: config[:logger],
    metrics_backend: config[:metrics],
    trace_backend: config[:traces]
  )

  # Start background stats reporter if pool exists
  return unless @pool

  @stats_thread = Thread.new do
    loop do
      sleep 60 # Report every minute
      @instrumentation.record_pool_stats(@pool.stats) if @pool
    rescue StandardError => e
      @instrumentation.logger.error(
        "Background stats thread error: #{e.class.name}: #{e.message}\n" \
        "Backtrace: #{e.backtrace.join("\n")}"
      )
      # Continue the loop to keep the thread alive
    end
  rescue StandardError => e
    @instrumentation.logger.fatal(
      "Background stats thread crashed: #{e.class.name}: #{e.message}\n" \
      "Backtrace: #{e.backtrace.join("\n")}"
    )
    # Thread will exit, but this prevents it from crashing silently
  end
end

#setup_webhooks(secret) ⇒ Object (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



171
172
173
# File 'lib/attio/enhanced_client.rb', line 171

private def setup_webhooks(secret)
  @webhooks = Webhooks.new(secret: secret)
end

#shutdown!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Graceful shutdown

Since:

  • 1.0.0



98
99
100
101
102
103
104
105
106
107
# File 'lib/attio/enhanced_client.rb', line 98

def shutdown!
  @pool&.shutdown
  @instrumentation&.disable!

  # Gracefully stop background stats thread
  return unless @stats_thread&.alive?

  @stats_thread.kill
  @stats_thread.join(5) # Wait up to 5 seconds for clean shutdown
end

#statsHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get comprehensive statistics

Since:

  • 1.0.0



88
89
90
91
92
93
94
95
# File 'lib/attio/enhanced_client.rb', line 88

def stats
  {
    pool: @pool&.stats,
    circuit_breaker: @circuit_breaker&.stats,
    rate_limiter: rate_limiter.status,
    instrumentation: @instrumentation&.metrics&.counters,
  }
end

#wrap_with_circuit_breaker(client) ⇒ Object (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



175
176
177
# File 'lib/attio/enhanced_client.rb', line 175

private def wrap_with_circuit_breaker(client)
  CircuitBreakerClient.new(client, @circuit_breaker)
end

#wrap_with_instrumentation(client) ⇒ Object (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 1.0.0



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/attio/enhanced_client.rb', line 179

private def wrap_with_instrumentation(client)
  # Create instrumented wrapper
  Class.new do
    def initialize(client, instrumentation)
      @client = client
      @instrumentation = instrumentation
    end

    i[get post patch put delete].each do |method|
      define_method(method) do |*args|
        start_time = Time.now
        path = args[0]

        begin
          result = @client.send(method, *args)
          status = result.is_a?(Hash) ? result["_status"] : nil
          @instrumentation.record_api_call(
            method: method,
            path: path,
            duration: Time.now - start_time,
            status: status
          )
          result
        rescue StandardError => e
          @instrumentation.record_api_call(
            method: method,
            path: path,
            duration: Time.now - start_time,
            error: e
          )
          raise
        end
      end
    end
  end.new(client, @instrumentation)
end