Skip to content

Commit

Permalink
feat(api): api update
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-bot committed Oct 16, 2024
1 parent 421c53d commit 2ab186f
Show file tree
Hide file tree
Showing 149 changed files with 5,693 additions and 121 deletions.
8 changes: 8 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ Style/RegexpLiteral:
Style/SymbolArray:
EnforcedStyle: brackets

# Prefer consistency in method calling syntax
Style/MethodCallWithArgsParentheses:
Enabled: true
Exclude:
- '**/*.gemspec'
IgnoredMethods:
- raise

# Nothing wrong with clear if statements.
Style/IfUnlessModifier:
Enabled: false
Expand Down
2 changes: 1 addition & 1 deletion .stats.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
configured_endpoints: 199
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/increase%2Fincrease-99be641e3d65ac2ac6b41a559599aac32f1bcf4e7bf379f52e8a4978b606aa09.yml
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/increase%2Fincrease-b366e6f11499b1be2d75c4081881132666f2e4aa2b4758f5153cee80596650ea.yml
2 changes: 2 additions & 0 deletions .yardopts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
--markup markdown
--reload
--load .yardopts.rb
9 changes: 9 additions & 0 deletions .yardopts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

YARD::Logger.class_eval do
log = instance_method(:log)
define_method(:log) do |severity, message|
return if message.start_with?("@param tag has unknown parameter name")
log.bind(self).call(severity, message)
end
end
56 changes: 40 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ account = increase.accounts.create(
program_id: "program_i2v2os4mwza1oetokh9i"
)

puts account.id
puts(account.id)
```

### Errors
Expand All @@ -60,31 +60,32 @@ non-success status code (i.e., 4xx or 5xx response), a subclass of
begin
account = increase.accounts.create
rescue Increase::HTTP::Error => e
puts e.code # 400
puts(e.code) # 400
end
```

Error codes are as followed:

| Status Code | Error Type |
| ----------- | -------------------------- |
| 400 | `BadRequestError` |
| 401 | `AuthenticationError` |
| 403 | `PermissionDeniedError` |
| 404 | `NotFoundError` |
| 409 | `ConflictError` |
| 422 | `UnprocessableEntityError` |
| 429 | `RateLimitError` |
| >=500 | `InternalServerError` |
| (else) | `APIStatusError` |
| N/A | `APIConnectionError` |
| Cause | Error Type |
| ---------------- | -------------------------- |
| HTTP 400 | `BadRequestError` |
| HTTP 401 | `AuthenticationError` |
| HTTP 403 | `PermissionDeniedError` |
| HTTP 404 | `NotFoundError` |
| HTTP 409 | `ConflictError` |
| HTTP 422 | `UnprocessableEntityError` |
| HTTP 429 | `RateLimitError` |
| HTTP >=500 | `InternalServerError` |
| Other HTTP error | `APIStatusError` |
| Timeout | `APITimeoutError` |
| Network error | `APIConnectionError` |

### Retries

Certain errors will be automatically retried 2 times by default, with a short
exponential backoff. Connection errors (for example, due to a network
connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit,
and >=500 Internal errors will all be retried by default.
connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, >=500 Internal errors,
and timeouts will all be retried by default.

You can use the `max_retries` option to configure or disable this:

Expand All @@ -103,6 +104,29 @@ increase.accounts.create(
)
```

### Timeouts

By default, requests will time out after 60 seconds.
Timeouts are applied separately to the initial connection and the overall request time,
so in some cases a request could wait 2\*timeout seconds before it fails.

You can use the `timeout` option to configure or disable this:

```ruby
# Configure the default for all requests:
increase = Increase::Client.new(
timeout: nil # default is 60
)

# Or, configure per-request:
increase.accounts.create(
name: "New Account!",
entity_id: "entity_n8y8tnk2p9339ti393yi",
program_id: "program_i2v2os4mwza1oetokh9i",
timeout: 5
)
```

## Versioning

This package follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions. As the
Expand Down
29 changes: 18 additions & 11 deletions lib/increase/base_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class BaseClient

def initialize(
base_url:,
timeout: nil,
headers: {},
max_retries: 0,
idempotency_header: nil
Expand All @@ -28,6 +29,7 @@ def initialize(
@port = base_url_parsed.port
@base_path = self.class.normalize_path(base_url_parsed.path)
@max_retries = max_retries
@timeout = timeout
@idempotency_header = idempotency_header
end

Expand Down Expand Up @@ -191,20 +193,19 @@ def header_based_retry(response)
rescue StandardError # rubocop:disable Lint/SuppressedException
end

def send_request(request, max_retries:, redirect_count:)
def send_request(request, max_retries:, timeout:, redirect_count:)
delay = 0.5
max_delay = 8.0
# Don't send the current retry count in the headers if the caller modified the header defaults.
should_send_retry_count = request[:headers]["x-stainless-retry-count"] == "0"
retries = 0
request_max_retries = max_retries || @max_retries
loop do # rubocop:disable Metrics/BlockLength
if should_send_retry_count
request[:headers]["x-stainless-retry-count"] = retries.to_s
end

begin
response = @requester.execute(request)
response = @requester.execute(request, timeout: timeout)
status = response.code.to_i

if status < 300
Expand Down Expand Up @@ -244,22 +245,23 @@ def send_request(request, max_retries:, redirect_count:)
return send_request(
request,
max_retries: max_retries,
timeout: timeout,
redirect_count: redirect_count + 1
)
end
rescue Net::HTTPBadResponse
if retries >= request_max_retries
message = "failed to complete the request within #{request_max_retries} retries"
if retries >= max_retries
message = "failed to complete the request within #{max_retries} retries"
raise HTTP::APIConnectionError.new(message: message, request: request)
end
rescue Timeout::Error
if retries >= request_max_retries
message = "failed to complete the request within #{request_max_retries} retries"
if retries >= max_retries
message = "failed to complete the request within #{max_retries} retries"
raise HTTP::APITimeoutError.new(message: message, request: request)
end
end

if !should_retry?(response) || retries >= request_max_retries
if (response && !should_retry?(response)) || retries >= max_retries
raise make_status_error_from_response(response)
end

Expand All @@ -273,10 +275,10 @@ def send_request(request, max_retries:, redirect_count:)
delay = (base_delay * jitter_factor).clamp(0, max_delay)
end

if response["x-stainless-mock-sleep"]
if response&.key?("x-stainless-mock-sleep")
request[:headers]["x-stainless-mock-slept"] = delay
else
sleep delay
sleep(delay)
end
end
end
Expand All @@ -289,7 +291,12 @@ def request(req, opts)
validate_request(req, opts)
options = Util.deep_merge(req, opts)
request_args = prep_request(options)
response = send_request(request_args, max_retries: opts[:max_retries], redirect_count: 0)
response = send_request(
request_args,
max_retries: opts.fetch(:max_retries, @max_retries),
timeout: opts.fetch(:timeout, @timeout),
redirect_count: 0
)
raw_data =
case response.content_type
when "application/json"
Expand Down
28 changes: 21 additions & 7 deletions lib/increase/base_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ def self.add_field(name_sym, type_info, mode)
rescue StandardError
name = self.class.name.split("::").last
raise ConversionError,
"Failed to parse #{name}.#{name_sym} as #{field_type.inspect}. To get the unparsed API response, use #{name}[:#{name_sym}]."
"Failed to parse #{name}.#{name_sym} as #{field_type.inspect}. " \
"To get the unparsed API response, use #{name}[:#{name_sym}]."
end
define_method("#{name_sym}=") { |val| @data[name_sym] = val }
end
Expand All @@ -141,7 +142,7 @@ def self.convert(data)
end

# Create a new instance of a model.
# @param data [Hash] Model attributes.
# @param data [Hash] Raw data to initialize the model with.
def initialize(data = {})
@data = {}
# TODO: what if data isn't a hash?
Expand All @@ -157,7 +158,7 @@ def initialize(data = {})
end

# Returns a Hash of the data underlying this object.
# Keys are Symbols and values are the parsed / typed domain objects.
# Keys are Symbols and values are the raw values from the response.
# The return value indicates which values were ever set on the object -
# i.e. there will be a key in this hash if they ever were, even if the
# set value was nil.
Expand All @@ -171,23 +172,36 @@ def to_h

alias_method :to_hash, :to_h

# Lookup attribute value by key in the object.
# If this key was not provided when the object was formed (e.g. because the API response
# did not include that key), returns nil.
# Returns the raw value associated with the given key, if found. Otherwise, nil is returned.
# It is valid to lookup keys that are not in the API spec, for example to access
# undocumented features.
# This method does not parse response data into higher-level types.
# Lookup by anything other than a Symbol is an ArgumentError.
#
# @param key [Symbol] Key to look up by.
#
# @return [Object] Parsed / typed value at the given key, or nil if no data is available.
# @return [Object, nil] The raw value at the given key.
def [](key)
if !key.instance_of?(Symbol)
raise ArgumentError, "Expected symbol key for lookup, got #{key.inspect}"
end
@data[key]
end

def deconstruct_keys(keys)
(keys || self.class.fields.keys).to_h do |k|
if !k.instance_of?(Symbol)
raise ArgumentError, "Expected symbol key for lookup, got #{k.inspect}"
end

if !self.class.fields.key?(k)
raise KeyError, "Expected one of #{self.class.fields.keys}, got #{k.inspect}"
end

[k, method(k).call]
end
end

# @return [String]
def inspect
"#<#{self.class.name}:0x#{object_id.to_s(16)} #{@data.inspect}>"
Expand Down
15 changes: 13 additions & 2 deletions lib/increase/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,13 @@ def auth_headers
# @param max_retries [Integer] Max number of retries to attempt after a failed retryable request.
#
# @return [Increase::Client]
def initialize(environment: nil, base_url: nil, api_key: nil, max_retries: DEFAULT_MAX_RETRIES)
def initialize(
environment: nil,
base_url: nil,
api_key: nil,
max_retries: DEFAULT_MAX_RETRIES,
timeout: 60
)
environments = {"production" => "https://api.increase.com", "sandbox" => "https://sandbox.increase.com"}
if environment && base_url
raise ArgumentError, "both environment and base_url given, expected only one"
Expand All @@ -206,7 +212,12 @@ def initialize(environment: nil, base_url: nil, api_key: nil, max_retries: DEFAU
raise ArgumentError, "api_key is required"
end

super(base_url: base_url, max_retries: max_retries, idempotency_header: idempotency_header)
super(
base_url: base_url,
max_retries: max_retries,
timeout: timeout,
idempotency_header: idempotency_header
)

@accounts = Increase::Resources::Accounts.new(client: self)
@account_numbers = Increase::Resources::AccountNumbers.new(client: self)
Expand Down
34 changes: 34 additions & 0 deletions lib/increase/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,40 @@ class Status < Increase::Enum
class Type < Increase::Enum
ACCOUNT = :account
end

# Create a new instance of Account from a Hash of raw data.
#
# @overload initialize(id: nil, bank: nil, closed_at: nil, created_at: nil, currency: nil, entity_id: nil, idempotency_key: nil, informational_entity_id: nil, interest_accrued: nil, interest_accrued_at: nil, interest_rate: nil, name: nil, program_id: nil, status: nil, type: nil)
# @param id [String] The Account identifier.
# @param bank [String] The bank the Account is with.
# @param closed_at [String] The [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) time at which the Account
# was closed.
# @param created_at [String] The [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) time at which the Account
# was created.
# @param currency [String] The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) code for the Account
# currency.
# @param entity_id [String] The identifier for the Entity the Account belongs to.
# @param idempotency_key [String] The idempotency key you chose for this object. This value is unique across
# Increase and is used to ensure that a request is only processed once. Learn more
# about [idempotency](https://increase.com/documentation/idempotency-keys).
# @param informational_entity_id [String] The identifier of an Entity that, while not owning the Account, is associated
# with its activity.
# @param interest_accrued [String] The interest accrued but not yet paid, expressed as a string containing a
# floating-point value.
# @param interest_accrued_at [String] The latest [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date on which
# interest was accrued.
# @param interest_rate [String] The Interest Rate currently being earned on the account, as a string containing
# a decimal number. For example, a 1% interest rate would be represented as
# "0.01".
# @param name [String] The name you choose for the Account.
# @param program_id [String] The identifier of the Program determining the compliance and commercial terms of
# this Account.
# @param status [String] The status of the Account.
# @param type [String] A constant representing the object's type. For this resource it will always be
# `account`.
def initialize(data = {})
super
end
end
end
end
Loading

0 comments on commit 2ab186f

Please sign in to comment.