iex-ruby-client 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71bec851d78d50e5e8625f18e94212bd3d5825a1bf90a3809258e6ef88dcae34
4
- data.tar.gz: b3f668404aada9eb2bcfa7f56767ce51083222c9f40a2fa95f15d005be9ee7ed
3
+ metadata.gz: b8a4a5e3e694cd4af9310002178f918ed6b6802711860e29780e2bde8f494805
4
+ data.tar.gz: 415aa05993daba620a4aea9a32aa52d73a8e0b3bdc39fd96296fb655703b2921
5
5
  SHA512:
6
- metadata.gz: bf29091889a722d957ea6abceb5dc60d761fde868abc4212829c48723fc668870dbad46a75815823224bf324bd466fc917669792b320c85b0a907979b37008cc
7
- data.tar.gz: 1222b56a8c47e79cd36b92b124128fa0162f8acbd9aa466f212cd0bc3bbe94036c2cc0c79243e348cf3b65ebb527ba47d46b91850dc8059cd081cab247568971
6
+ metadata.gz: 43680c128f3354b4f64f485c51ddfed0f94c9a3f25b6be1c16f93a7a3a97534a0e1fa990a85394d92c1aca220410ceea1a9e9f0d061abce8c7e697ef91a022e3
7
+ data.tar.gz: 3f497b0bdcc8b295b1f370d3185e6768fea91cbafdde5acdf637d8a16b918d0e5fc229eb5bb702eca8d5848388b5803e797cf8c21feb156c895f3c8e1d5d2dd8
@@ -0,0 +1,2 @@
1
+ IEX_API_PUBLISHABLE_TOKEN=test-iex-api-publishable-token
2
+ IEX_API_SECRET_TOKEN=test-iex-api-secret-token
@@ -11,11 +11,3 @@
11
11
  Style/AsciiComments:
12
12
  Exclude:
13
13
  - 'lib/iex/resources/quote.rb'
14
-
15
- # Offense count: 1
16
- # Cop supports --auto-correct.
17
- # Configuration parameters: EnforcedStyle, Autocorrect.
18
- # SupportedStyles: module_function, extend_self
19
- Style/ModuleFunction:
20
- Exclude:
21
- - 'lib/iex/api/config.rb'
@@ -1,3 +1,12 @@
1
+ ### 1.3.0 (2020/10/31)
2
+
3
+ * [#82](https://github.com/dblock/iex-ruby-client/pull/82): Added `config.referer` to set HTTP `Referer` header. This enables IEX's "Manage domains" domain locking for tokens - [@agrberg](https://github.com/agrberg).
4
+ * [#70](https://github.com/dblock/iex-ruby-client/pull/70): Added support for `ref_data_isin` taking a single `String` parameter - [@rodolfobandeira](https://github.com/rodolfobandeira).
5
+ * [#84](https://github.com/dblock/iex-ruby-client/pull/84): Added support for Advanced Stats API - [@FanaHOVA](https://github.com/fanahova).
6
+ * [#86](https://github.com/dblock/iex-ruby-client/issues/86): Added advanced logger configuration to `config.logger`. Now a hash with keys `:instance, :options, and :proc` can be passed and used directly with Faraday - [@agrberg](https://github.com/agrberg).
7
+ * [#88](https://github.com/dblock/iex-ruby-client/pull/88): Updated logger configuration to work like `Config` class and allow attribute and block based configuration - [@agrberg](https://github.com/agrberg).
8
+ * [#89](https://github.com/dblock/iex-ruby-client/pull/89): Backfill breaking `Config#logger` setting change from [#88](https://github.com/dblock/iex-ruby-client/pull/88) - [@agrberg](https://github.com/agrberg).
9
+
1
10
  ### 1.2.0 (2020/09/01)
2
11
 
3
12
  * [#78](https://github.com/dblock/iex-ruby-client/pull/78): Added `Quote#extended_change_percent` and `Quote#extended_change_percent_s` properties to Quote - [@reddavis](https://github.com/reddavis).
data/Gemfile CHANGED
@@ -5,5 +5,6 @@ gemspec
5
5
  group :test do
6
6
  gem 'danger-changelog', '~> 0.6.0'
7
7
  gem 'danger-toc', '~> 0.2.0'
8
+ gem 'dotenv'
8
9
  gem 'pry'
9
10
  end
data/README.md CHANGED
@@ -31,6 +31,7 @@ A Ruby client for the [The IEX Cloud API](https://iexcloud.io/docs/api/).
31
31
  - [Get List](#get-list)
32
32
  - [Other Requests](#other-requests)
33
33
  - [Configuration](#configuration)
34
+ - [Logging](#logging)
34
35
  - [Sandbox Environment](#sandbox-environment)
35
36
  - [Errors](#errors)
36
37
  - [SymbolNotFound](#symbolnotfound)
@@ -378,10 +379,10 @@ See [#crypto](https://iexcloud.io/docs/api/#crypto) for detailed documentation o
378
379
 
379
380
  ### ISIN Mapping
380
381
 
381
- Converts ISIN to IEX Cloud symbols.
382
+ Convert ISIN to IEX Cloud symbols.
382
383
 
383
384
  ```ruby
384
- symbols = client.ref_data_isin(['US0378331005'])
385
+ symbols = client.ref_data_isin('US0378331005')
385
386
 
386
387
  symbols.first.exchange # NAS
387
388
  symbols.first.iex_id # IEX_4D48333344362D52
@@ -389,6 +390,13 @@ symbols.first.region # US
389
390
  symbols.first.symbol # AAPL
390
391
  ```
391
392
 
393
+ The API also lets you convert multiple ISINs to IEX Cloud symbols.
394
+
395
+ ```ruby
396
+ symbols = client.ref_data_isin(['US0378331005', 'US0378331006'])
397
+ ```
398
+
399
+
392
400
  You can use `mapped: true` option to receive symbols grouped by their ISINs.
393
401
 
394
402
  ```ruby
@@ -437,7 +445,7 @@ client.post('ref-data/isin', isin: ['US0378331005'], token: 'secret_token') # [{
437
445
  You can configure client options globally or directly with a `IEX::Api::Client` instance.
438
446
 
439
447
  ```ruby
440
- IEX::Api::Client.configure do |config|
448
+ IEX::Api.configure do |config|
441
449
  config.publishable_token = ENV['IEX_API_PUBLISHABLE_TOKEN']
442
450
  config.endpoint = 'https://sandbox.iexapis.com/v1' # use sandbox environment
443
451
  end
@@ -458,17 +466,42 @@ user_agent | User-agent, defaults to _IEX Ruby Client/version_.
458
466
  proxy | Optional HTTP proxy.
459
467
  ca_path | Optional SSL certificates path.
460
468
  ca_file | Optional SSL certificates file.
461
- logger | Optional `Logger` instance that logs HTTP requests.
469
+ logger | Optional `Logger` instance or logger configuration to log HTTP requests.
462
470
  timeout | Optional open/read timeout in seconds.
463
471
  open_timeout | Optional connection open timeout in seconds.
464
472
  publishable_token | IEX Cloud API publishable token.
465
473
  endpoint | Defaults to `https://cloud.iexapis.com/v1`.
474
+ referer | Optional string for HTTP `Referer` header, enables token domain management.
475
+
476
+ ### Logging
477
+
478
+ Faraday will not log HTTP requests by default. In order to do this you can either provide a `logger` instance or configuration attributes to `IEX::Api::Client`. Configuration allows you to supply the `instance`, `options`, and `proc` to [Faraday](https://lostisland.github.io/faraday/middleware/logger#include-and-exclude-headersbodies).
479
+
480
+ ```ruby
481
+ logger_instance = Logger.new(STDOUT)
482
+
483
+ IEX::Api.configure do |config|
484
+ config.logger.instance = logger_instance
485
+ config.logger.options = { bodies: true }
486
+ config.logger.proc = proc { |logger| logger.filter(/T?[sp]k_\w+/i, '[REMOVED]') }
487
+ end
488
+ # or
489
+ IEX::Api.logger do |logger|
490
+ logger.instance = logger_instance
491
+ logger.options = …
492
+ logger.proc = …
493
+ end
494
+ # or
495
+ IEX::Api.logger = logger_instance
496
+ # or
497
+ IEX::Api::Client.new(logger: logger_instance)
498
+ ```
466
499
 
467
500
  ## Sandbox Environment
468
501
 
469
502
  IEX recommends you use a sandbox token and endpoint for testing.
470
503
 
471
- However, please note that data in the IEX sandbox environment is scrambled. Therefore elements such as company and people names, descriptions, tags, and website URLs don't render any coherent data.
504
+ However, please note that data in the IEX sandbox environment is scrambled. Therefore elements such as company and people names, descriptions, tags, and website URLs don't render any coherent data. In addition, results, such as closing market prices and dividend yield, are not accurate and vary on every call.
472
505
 
473
506
  See [IEX sandbox environment](https://intercom.help/iexcloud/en/articles/2915433-testing-with-the-iex-cloud-sandbox) for more information.
474
507
 
@@ -1,3 +1,4 @@
1
+ require_relative 'endpoints/advanced_stats'
1
2
  require_relative 'endpoints/chart'
2
3
  require_relative 'endpoints/company'
3
4
  require_relative 'endpoints/dividends'
@@ -15,5 +16,6 @@ require_relative 'endpoints/crypto'
15
16
  require_relative 'endpoints/ref_data'
16
17
  require_relative 'endpoints/stock_market'
17
18
 
18
- require_relative 'api/config'
19
+ require_relative 'api/config/logger'
20
+ require_relative 'api/config/client'
19
21
  require_relative 'api/client'
@@ -1,6 +1,10 @@
1
1
  module IEX
2
2
  module Api
3
+ extend Config::Client::Accessor
4
+ extend Config::Logger::Accessor
5
+
3
6
  class Client
7
+ include Endpoints::AdvancedStats
4
8
  include Endpoints::Chart
5
9
  include Endpoints::Company
6
10
  include Endpoints::Crypto
@@ -21,12 +25,16 @@ module IEX
21
25
  include Cloud::Connection
22
26
  include Cloud::Request
23
27
 
24
- include Config
28
+ attr_accessor(*Config::Client::ATTRIBUTES)
29
+
30
+ attr_reader :logger
25
31
 
26
32
  def initialize(options = {})
27
- Config::ATTRIBUTES.each do |key|
33
+ Config::Client::ATTRIBUTES.each do |key|
28
34
  send("#{key}=", options[key] || IEX::Api.config.send(key))
29
35
  end
36
+ @logger = Config::Logger.dup
37
+ @logger.instance = options[:logger] if options.key?(:logger)
30
38
  end
31
39
  end
32
40
  end
@@ -0,0 +1,52 @@
1
+ module IEX
2
+ module Api
3
+ module Config
4
+ module Client
5
+ ATTRIBUTES = %i[
6
+ ca_file
7
+ ca_path
8
+ endpoint
9
+ open_timeout
10
+ proxy
11
+ publishable_token
12
+ referer
13
+ secret_token
14
+ timeout
15
+ user_agent
16
+ ].freeze
17
+
18
+ class << self
19
+ include Config::Logger::Accessor
20
+
21
+ attr_accessor(*ATTRIBUTES)
22
+
23
+ def reset!
24
+ self.ca_file = defined?(OpenSSL) ? OpenSSL::X509::DEFAULT_CERT_FILE : nil
25
+ self.ca_path = defined?(OpenSSL) ? OpenSSL::X509::DEFAULT_CERT_DIR : nil
26
+ self.endpoint = 'https://cloud.iexapis.com/v1'
27
+ self.publishable_token = ENV['IEX_API_PUBLISHABLE_TOKEN']
28
+ self.secret_token = ENV['IEX_API_SECRET_TOKEN']
29
+ self.user_agent = "IEX Ruby Client/#{IEX::VERSION}"
30
+
31
+ self.open_timeout = nil
32
+ self.proxy = nil
33
+ self.referer = nil
34
+ self.timeout = nil
35
+ end
36
+ end
37
+
38
+ module Accessor
39
+ def configure
40
+ block_given? ? yield(Config::Client) : Config::Client
41
+ end
42
+
43
+ def config
44
+ Config::Client
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ IEX::Api::Config::Client.reset!
@@ -0,0 +1,35 @@
1
+ module IEX
2
+ module Api
3
+ module Config
4
+ module Logger
5
+ ATTRIBUTES = %i[
6
+ instance
7
+ options
8
+ proc
9
+ ].freeze
10
+
11
+ class << self
12
+ attr_accessor(*ATTRIBUTES)
13
+
14
+ def reset!
15
+ self.instance = nil
16
+ self.options = {}
17
+ self.proc = nil
18
+ end
19
+ end
20
+
21
+ module Accessor
22
+ def logger
23
+ block_given? ? yield(Config::Logger) : Config::Logger
24
+ end
25
+
26
+ def logger=(instance)
27
+ logger.instance = instance
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ IEX::Api::Config::Logger.reset!
@@ -15,6 +15,7 @@ module IEX
15
15
  options[:headers]['Accept'] = 'application/json; charset=utf-8'
16
16
  options[:headers]['Content-Type'] = 'application/json; charset=utf-8'
17
17
  options[:headers]['User-Agent'] = user_agent if user_agent
18
+ options[:headers]['Referer'] = referer if referer
18
19
  options[:proxy] = proxy if proxy
19
20
  options[:ssl] = { ca_path: ca_path, ca_file: ca_file } if ca_path || ca_file
20
21
 
@@ -29,7 +30,7 @@ module IEX
29
30
  connection.use ::Faraday::Request::UrlEncoded
30
31
  connection.use ::IEX::Cloud::Response::RaiseError
31
32
  connection.use ::FaradayMiddleware::ParseJson, content_type: /\bjson$/
32
- connection.response :logger, logger if logger
33
+ connection.response(:logger, logger.instance, logger.options, &logger.proc) if logger.instance
33
34
  connection.adapter ::Faraday.default_adapter
34
35
  end
35
36
  end
@@ -0,0 +1,11 @@
1
+ module IEX
2
+ module Endpoints
3
+ module AdvancedStats
4
+ def advanced_stats(symbol, options = {})
5
+ IEX::Resources::AdvancedStats.new(get("stock/#{symbol}/advanced-stats", { token: publishable_token }.merge(options)))
6
+ rescue Faraday::ResourceNotFound => e
7
+ raise IEX::Errors::SymbolNotFoundError.new(symbol, e.response[:body])
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,7 +2,7 @@ module IEX
2
2
  module Endpoints
3
3
  module RefData
4
4
  def ref_data_isin(isins, options = {})
5
- response = get('ref-data/isin', { token: publishable_token, isin: isins.join(',') }.merge(options))
5
+ response = get('ref-data/isin', { token: publishable_token, isin: Array(isins).join(',') }.merge(options))
6
6
 
7
7
  if response.is_a?(Hash) # mapped option was set
8
8
  response.transform_values do |rows|
@@ -1,5 +1,6 @@
1
1
  require_relative 'resources/resource'
2
2
 
3
+ require_relative 'resources/advanced_stats'
3
4
  require_relative 'resources/chart'
4
5
  require_relative 'resources/company'
5
6
  require_relative 'resources/dividends'
@@ -0,0 +1,42 @@
1
+ module IEX
2
+ module Resources
3
+ class AdvancedStats < Resource
4
+ property 'total_cash', from: 'totalCash'
5
+ property 'total_cash_dollars', from: 'totalCash', with: ->(v) { Resource.to_dollar(amount: v) }
6
+ property 'current_debt', from: 'currentDebt'
7
+ property 'current_debt_dollars', from: 'currentDebt', with: ->(v) { Resource.to_dollar(amount: v) }
8
+ property 'revenue'
9
+ property 'revenue_dollars', from: 'revenue', with: ->(v) { Resource.to_dollar(amount: v) }
10
+ property 'gross_profit', from: 'grossProfit'
11
+ property 'gross_profit_dollar', from: 'grossProfit', with: ->(v) { Resource.to_dollar(amount: v) }
12
+ property 'total_revenue', from: 'totalRevenue'
13
+ property 'total_revenue_dollar', from: 'totalRevenue', with: ->(v) { Resource.to_dollar(amount: v) }
14
+ property 'ebitda', from: 'EBITDA'
15
+ property 'ebitda_dollar', from: 'EBITDA', with: ->(v) { Resource.to_dollar(amount: v) }
16
+ property 'revenue_per_share', from: 'revenuePerShare'
17
+ property 'revenue_per_share_dollars', from: 'revenuePerShare', with: ->(v) { Resource.to_dollar(amount: v) }
18
+ property 'revenue_per_employee', from: 'revenuePerEmployee'
19
+ property 'revenue_per_employee_dollar', from: 'revenuePerEmployee', with: ->(v) { Resource.to_dollar(amount: v) }
20
+ property 'debt_to_equity', from: 'debtToEquity'
21
+ property 'profit_margin', from: 'profitMargin'
22
+ property 'enterprise_value', from: 'enterpriseValue'
23
+ property 'enterprise_value_dollar', from: 'enterpriseValue', with: ->(v) { Resource.to_dollar(amount: v) }
24
+ property 'enterprise_value_to_revenue', from: 'enterpriseValueToRevenue'
25
+ property 'price_to_sales', from: 'priceToSales'
26
+ property 'price_to_sales_dollar', from: 'priceToSales', with: ->(v) { Resource.to_dollar(amount: v) }
27
+ property 'price_to_book', from: 'priceToBook'
28
+ property 'forward_pe_ratio', from: 'forwardPERatio'
29
+ property 'pe_high', from: 'peHigh'
30
+ property 'pe_low', from: 'peLow'
31
+ property 'avg_30_volume', from: 'avg30Volume'
32
+ property 'peg_ratio', from: 'pegRatio'
33
+ property 'week_52_high_date', from: 'week52highDate'
34
+ property 'week_52_low_date', from: 'week52lowDate'
35
+ property 'beta'
36
+
37
+ def initialize(data = {})
38
+ super
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,3 +1,3 @@
1
1
  module IEX
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '1.3.0'.freeze
3
3
  end
@@ -0,0 +1,50 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://cloud.iexapis.com/v1/stock/INVALID/advanced-stats?token=test-iex-api-publishable-token
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - application/json; charset=utf-8
12
+ Content-Type:
13
+ - application/json; charset=utf-8
14
+ User-Agent:
15
+ - IEX Ruby Client/1.2.1
16
+ Accept-Encoding:
17
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
18
+ response:
19
+ status:
20
+ code: 404
21
+ message: Not Found
22
+ headers:
23
+ Server:
24
+ - nginx
25
+ Date:
26
+ - Tue, 20 Oct 2020 23:57:05 GMT
27
+ Content-Type:
28
+ - text/html; charset=utf-8
29
+ Transfer-Encoding:
30
+ - chunked
31
+ Connection:
32
+ - keep-alive
33
+ Set-Cookie:
34
+ - ctoken=aa7b4e3cbfee43e0a6d8075ad7d24f32; Max-Age=43200; Path=/; Expires=Wed,
35
+ 21 Oct 2020 11:57:05 GMT
36
+ Strict-Transport-Security:
37
+ - max-age=15768000
38
+ Access-Control-Allow-Origin:
39
+ - "*"
40
+ Access-Control-Allow-Credentials:
41
+ - 'true'
42
+ Access-Control-Allow-Methods:
43
+ - GET, OPTIONS
44
+ Access-Control-Allow-Headers:
45
+ - Origin, X-Requested-With, Content-Type, Accept, Request-Source
46
+ body:
47
+ encoding: ASCII-8BIT
48
+ string: Unknown symbol
49
+ recorded_at: Tue, 20 Oct 2020 23:57:05 GMT
50
+ recorded_with: VCR 6.0.0
@@ -0,0 +1,57 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://cloud.iexapis.com/v1/stock/MSFT/advanced-stats?token=test-iex-api-publishable-token
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - application/json; charset=utf-8
12
+ Content-Type:
13
+ - application/json; charset=utf-8
14
+ User-Agent:
15
+ - IEX Ruby Client/1.2.1
16
+ Accept-Encoding:
17
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Server:
24
+ - nginx
25
+ Date:
26
+ - Tue, 20 Oct 2020 23:57:04 GMT
27
+ Content-Type:
28
+ - application/json; charset=utf-8
29
+ Transfer-Encoding:
30
+ - chunked
31
+ Connection:
32
+ - keep-alive
33
+ Set-Cookie:
34
+ - ctoken=767762d0898c40be8baf5aaf659ee42b; Max-Age=43200; Path=/; Expires=Wed,
35
+ 21 Oct 2020 11:57:04 GMT
36
+ Iexcloud-Messages-Used:
37
+ - '3005'
38
+ Iexcloud-Premium-Messages-Used:
39
+ - '0'
40
+ X-Content-Type-Options:
41
+ - nosniff
42
+ Strict-Transport-Security:
43
+ - max-age=15768000
44
+ Access-Control-Allow-Origin:
45
+ - "*"
46
+ Access-Control-Allow-Credentials:
47
+ - 'true'
48
+ Access-Control-Allow-Methods:
49
+ - GET, OPTIONS
50
+ Access-Control-Allow-Headers:
51
+ - Origin, X-Requested-With, Content-Type, Accept, Request-Source
52
+ body:
53
+ encoding: ASCII-8BIT
54
+ string: '{"week52change":0.533539,"week52high":232.86,"week52low":132.52,"marketcap":1621141983000,"employees":163000,"day200MovingAvg":187.72,"day50MovingAvg":212.09,"float":7454581741,"avg10Volume":26989991,"avg30Volume":31404571.1,"ttmEPS":5.8207,"ttmDividendRate":2.04,"companyName":"Microsoft
55
+ Corp.","sharesOutstanding":7567650000,"maxChangePercent":7.1248,"year5ChangePercent":3.3093,"year2ChangePercent":1.0108,"year1ChangePercent":0.572482,"ytdChangePercent":0.367576,"month6ChangePercent":0.229899,"month3ChangePercent":0.082709,"month1ChangePercent":0.082549,"day30ChangePercent":0.025251,"day5ChangePercent":-0.007859,"nextDividendDate":"2020-11-18","dividendYield":0.009522920362244423,"nextEarningsDate":"2020-10-27","exDividendDate":"2020-08-19","peRatio":36.88,"beta":1.1472751624864646,"totalCash":136527000000,"currentDebt":5905000000,"revenue":143015000000,"grossProfit":96937000000,"totalRevenue":143015000000,"EBITDA":65259000000,"revenuePerShare":18.9,"revenuePerEmployee":877392.64,"debtToEquity":0.69,"profitMargin":0.3096248645247002,"enterpriseValue":1607892999000,"enterpriseValueToRevenue":11.24,"priceToSales":11.62,"priceToBook":13.703188252299162,"forwardPERatio":null,"pegRatio":5.6,"peHigh":40.49739130434783,"peLow":23.046956521739133,"week52highDate":"2020-09-02","week52lowDate":"2020-03-23","putCallRatio":0.36251766583920975}'
56
+ recorded_at: Tue, 20 Oct 2020 23:57:04 GMT
57
+ recorded_with: VCR 6.0.0
@@ -3,6 +3,7 @@ require 'spec_helper'
3
3
  describe IEX::Api::Client do
4
4
  before do
5
5
  IEX::Api.config.reset!
6
+ IEX::Api.logger.reset!
6
7
  end
7
8
  context 'with defaults' do
8
9
  let(:client) { described_class.new }
@@ -14,7 +15,7 @@ describe IEX::Api::Client do
14
15
  expect(client.user_agent).to eq "IEX Ruby Client/#{IEX::VERSION}"
15
16
  end
16
17
  it 'sets user-agent' do
17
- expect(client.user_agent).to eq IEX::Api::Config.user_agent
18
+ expect(client.user_agent).to eq IEX::Api::Config::Client.user_agent
18
19
  expect(client.user_agent).to include IEX::VERSION
19
20
  end
20
21
  it 'caches the Faraday connection to allow persistent adapters' do
@@ -22,20 +23,24 @@ describe IEX::Api::Client do
22
23
  second = client.send(:connection)
23
24
  expect(first).to equal second
24
25
  end
25
- (IEX::Api::Config::ATTRIBUTES - [:logger]).each do |key|
26
+ it 'sets a nil logger' do
27
+ expect(client.logger.instance).to be_nil
28
+ expect(client.send(:connection).builder.handlers).not_to include(::Faraday::Response::Logger)
29
+ end
30
+ IEX::Api::Config::Client::ATTRIBUTES.each do |key|
26
31
  it "sets #{key}" do
27
- expect(client.send(key)).to eq IEX::Api::Config.send(key)
32
+ expect(client.send(key)).to eq IEX::Api::Config::Client.send(key)
28
33
  end
29
34
  end
30
35
  end
31
36
  end
32
37
  context 'with custom settings' do
33
38
  context '#initialize' do
34
- IEX::Api::Config::ATTRIBUTES.each do |key|
39
+ IEX::Api::Config::Client::ATTRIBUTES.each do |key|
35
40
  context key.to_s do
36
41
  let(:client) { described_class.new(key => 'custom') }
37
42
  it "sets #{key}" do
38
- expect(client.send(key)).to_not eq IEX::Api::Config.send(key)
43
+ expect(client.send(key)).to_not eq IEX::Api::Config::Client.send(key)
39
44
  expect(client.send(key)).to eq 'custom'
40
45
  end
41
46
  end
@@ -96,22 +101,74 @@ describe IEX::Api::Client do
96
101
  end
97
102
  end
98
103
  end
104
+
99
105
  context 'logger option' do
100
106
  let(:logger) { Logger.new(STDOUT) }
101
- before do
102
- IEX::Api.configure do |config|
103
- config.logger = logger
107
+
108
+ after { IEX::Api.logger.reset! }
109
+
110
+ context 'when assigning an instance' do
111
+ context '#initialize' do
112
+ context 'when directly assigning `logger`' do
113
+ before { IEX::Api.logger = logger }
114
+
115
+ it 'sets logger' do
116
+ expect(client.logger.instance).to eq(logger)
117
+ end
118
+
119
+ it 'creates a connection with a logger' do
120
+ expect(client.send(:connection).builder.handlers).to include ::Faraday::Response::Logger
121
+ end
122
+ end
123
+
124
+ context 'when assigning through `configure.logger`' do
125
+ it 'sets the logger' do
126
+ IEX::Api.configure.logger = logger
127
+ expect(client.logger.instance).to eq(logger)
128
+ end
129
+ end
130
+
131
+ context 'when passing in at initialization' do
132
+ it 'sets the logger' do
133
+ client = described_class.new(logger: logger)
134
+ expect(client.logger.instance).to eq(logger)
135
+ end
136
+
137
+ it 'can overwrite a set logger' do
138
+ IEX::Api.logger = logger
139
+ client = described_class.new(logger: nil)
140
+ expect(client.logger.instance).to be_nil
141
+ end
142
+ end
104
143
  end
105
144
  end
106
- context '#initialize' do
107
- it 'sets logger' do
108
- expect(client.logger).to eq logger
145
+
146
+ context 'when assigning a configuration' do
147
+ let(:opts) { { bodies: true } }
148
+ let(:proc_arg) { proc {} }
149
+
150
+ before do
151
+ IEX::Api.logger do |log_config|
152
+ log_config.instance = logger
153
+ log_config.options = opts
154
+ log_config.proc = proc_arg
155
+ end
109
156
  end
110
- it 'creates a connection with a logger' do
111
- expect(client.send(:connection).builder.handlers).to include ::Faraday::Response::Logger
157
+
158
+ context '#initialize' do
159
+ it 'sets logger' do
160
+ expect(client.logger.instance).to eq logger
161
+ expect(client.logger.options).to eq opts
162
+ expect(client.logger.proc).to eq proc_arg
163
+ end
164
+
165
+ it 'creates a connection with a logger' do
166
+ expect(client.send(:connection).builder.handlers).to include ::Faraday::Response::Logger
167
+ end
112
168
  end
113
169
  end
114
170
  end
171
+
115
172
  context 'timeout options' do
116
173
  before do
117
174
  IEX::Api.configure do |config|
@@ -131,6 +188,27 @@ describe IEX::Api::Client do
131
188
  end
132
189
  end
133
190
  end
191
+
192
+ context 'when resetting/changing configuration' do
193
+ before do
194
+ IEX::Api.configure { |config| config.user_agent = 'custom/user-agent' }
195
+ end
196
+
197
+ it 'does not reset the client' do
198
+ expect { IEX::Api.config.reset! }.not_to change(client, :user_agent).from('custom/user-agent')
199
+ end
200
+
201
+ it 'effects the next client' do
202
+ pre_config_client = described_class.new
203
+ IEX::Api.configure { |config| config.user_agent = 'custom/user-agent-2' }
204
+ expect(described_class.new.user_agent).not_to eq(pre_config_client.user_agent)
205
+ end
206
+
207
+ it 'should not allow the client to reset' do
208
+ expect { client.reset! }.to raise_error(NoMethodError)
209
+ end
210
+ end
211
+
134
212
  context 'without a token' do
135
213
  let(:client) { described_class.new }
136
214
  it 'results in an API key error', vcr: { cassette_name: 'client/access_denied' } do
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe IEX::Api::Config::Client do
4
+ before do
5
+ IEX::Api.config.reset!
6
+ end
7
+ describe '#defaults' do
8
+ it 'sets endpoint' do
9
+ expect(IEX::Api.config.endpoint).to eq 'https://cloud.iexapis.com/v1'
10
+ end
11
+ end
12
+ describe '#configure' do
13
+ before do
14
+ IEX::Api.configure do |config|
15
+ config.endpoint = 'updated'
16
+ end
17
+ end
18
+ it 'sets endpoint' do
19
+ expect(IEX::Api.config.endpoint).to eq 'updated'
20
+ end
21
+ end
22
+
23
+ context 'when configuring the logger' do
24
+ after { IEX::Api.configure.logger.reset! }
25
+
26
+ let(:logger) { Logger.new(STDOUT) }
27
+
28
+ describe '#logger=' do
29
+ it 'updates IEX::Api.config correctly' do
30
+ expect do
31
+ IEX::Api.configure { |config| config.logger = logger }
32
+ end.to change(IEX::Api.config.logger, :instance).from(nil).to(logger)
33
+ end
34
+
35
+ it 'updates IEX::Api.logger correctly' do
36
+ expect do
37
+ IEX::Api.configure { |config| config.logger = logger }
38
+ end.to change(IEX::Api.logger, :instance).from(nil).to(logger)
39
+ end
40
+ end
41
+
42
+ describe '#logger' do
43
+ it 'accesses the current logger' do
44
+ expect { IEX::Api.logger = logger }
45
+ .to change(IEX::Api.config.logger, :instance).from(nil).to(logger)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe IEX::Api::Config::Logger do
4
+ after { IEX::Api.logger.reset! }
5
+
6
+ describe '#defaults' do
7
+ it 'sets the default values' do
8
+ expect(IEX::Api.logger.instance).to be_nil
9
+ expect(IEX::Api.logger.options).to eq({})
10
+ expect(IEX::Api.logger.proc).to be_nil
11
+ end
12
+ end
13
+
14
+ describe '#logger' do
15
+ let(:logger_instance) { Logger.new(STDOUT) }
16
+ let(:opts) { { bodies: true } }
17
+ let(:proc_arg) { proc {} }
18
+
19
+ it 'allows setting the instance directly' do
20
+ expect { IEX::Api.logger = logger_instance }
21
+ .to change(IEX::Api::Config::Logger, :instance).to(logger_instance)
22
+ end
23
+
24
+ context 'when configuing' do
25
+ after do
26
+ expect(IEX::Api.logger.instance).to eq(logger_instance)
27
+ expect(IEX::Api.logger.options).to eq(opts)
28
+ expect(IEX::Api.logger.proc).to eq(proc_arg)
29
+ end
30
+
31
+ it 'sets via assignment' do
32
+ IEX::Api.logger.instance = logger_instance
33
+ IEX::Api.logger.options = opts
34
+ IEX::Api.logger.proc = proc_arg
35
+ end
36
+
37
+ it 'sets via block' do
38
+ IEX::Api.logger do |logger|
39
+ logger.instance = logger_instance
40
+ logger.options = opts
41
+ logger.proc = proc_arg
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe IEX::Resources::AdvancedStats do
4
+ include_context 'client'
5
+
6
+ context 'general information', vcr: { cassette_name: 'advanced_stats/msft' } do
7
+ subject do
8
+ client.advanced_stats('MSFT')
9
+ end
10
+
11
+ it 'fetches advanced stats' do
12
+ expect(subject.total_cash).to eq 136_527_000_000
13
+ expect(subject.total_cash_dollars).to eq '$136,527,000,000'
14
+ expect(subject.current_debt).to eq 5_905_000_000
15
+ expect(subject.current_debt_dollars).to eq '$5,905,000,000'
16
+ expect(subject.revenue).to eq 143_015_000_000
17
+ expect(subject.revenue_dollars).to eq '$143,015,000,000'
18
+ expect(subject.gross_profit).to eq 96_937_000_000
19
+ expect(subject.gross_profit_dollar).to eq '$96,937,000,000'
20
+ expect(subject.total_revenue).to eq 143_015_000_000
21
+ expect(subject.total_revenue_dollar).to eq '$143,015,000,000'
22
+ expect(subject.ebitda).to eq 65_259_000_000
23
+ expect(subject.ebitda_dollar).to eq '$65,259,000,000'
24
+ expect(subject.revenue_per_share).to eq 18.9
25
+ expect(subject.revenue_per_share_dollars).to eq '$18'
26
+ expect(subject.revenue_per_employee).to eq 877_392.64
27
+ expect(subject.revenue_per_employee_dollar).to eq '$877,392'
28
+ expect(subject.debt_to_equity).to eq 0.69
29
+ expect(subject.profit_margin).to eq 0.3096248645247002
30
+ expect(subject.enterprise_value).to eq 1_607_892_999_000
31
+ expect(subject.enterprise_value_dollar).to eq '$1,607,892,999,000'
32
+ expect(subject.enterprise_value_to_revenue).to eq 11.24
33
+ expect(subject.price_to_sales).to eq 11.62
34
+ expect(subject.price_to_sales_dollar).to eq '$11'
35
+ expect(subject.price_to_book).to eq 13.703188252299162
36
+ expect(subject.forward_pe_ratio).to eq nil
37
+ expect(subject.peg_ratio).to eq 5.6
38
+ expect(subject.pe_high).to eq 40.49739130434783
39
+ expect(subject.week_52_high_date).to eq '2020-09-02'
40
+ expect(subject.pe_low).to eq 23.046956521739133
41
+ expect(subject.week_52_low_date).to eq '2020-03-23'
42
+ end
43
+ end
44
+
45
+ context 'invalid symbol', vcr: { cassette_name: 'advanced_stats/invalid' } do
46
+ subject do
47
+ client.advanced_stats('INVALID')
48
+ end
49
+
50
+ it 'fails with SymbolNotFoundError' do
51
+ expect { subject }.to raise_error IEX::Errors::SymbolNotFoundError, 'Symbol INVALID Not Found'
52
+ end
53
+ end
54
+ end
@@ -14,6 +14,16 @@ describe IEX::Api::Client do
14
14
  end
15
15
  end
16
16
 
17
+ context 'graciously handle parameter as string', vcr: { cassette_name: 'ref-data/isin' } do
18
+ subject { client.ref_data_isin('US0378331005') }
19
+
20
+ it 'converts ISIN to IEX Cloud symbols' do
21
+ expect(subject.count).to eq(2)
22
+ expect(subject.first).to eq('exchange' => 'NAS', 'iex_id' => 'IEX_4D48333344362D52', 'region' => 'US', 'symbol' => 'AAPL')
23
+ expect(subject.last).to eq('exchange' => 'ETR', 'iex_id' => 'IEX_464D46474C312D52', 'region' => 'DE', 'symbol' => 'APC-GY')
24
+ end
25
+ end
26
+
17
27
  context 'with mapped option', vcr: { cassette_name: 'ref-data/isin_mapped' } do
18
28
  subject { client.ref_data_isin(%w[US0378331005 US5949181045], mapped: true) }
19
29
 
@@ -27,13 +37,13 @@ describe IEX::Api::Client do
27
37
  expect(subject['US5949181045'][1]).to eq('exchange' => 'ETR', 'iex_id' => 'IEX_4C42583859482D52', 'region' => 'DE', 'symbol' => 'MSF-GY')
28
38
  expect(subject['US5949181045'][2]).to eq('exchange' => 'BRU', 'iex_id' => 'IEX_5833345950432D52', 'region' => 'BE', 'symbol' => 'MSF-BB')
29
39
  end
40
+ end
30
41
 
31
- context 'with wrong ISIN', vcr: { cassette_name: 'ref-data/wrong_isin_mapped' } do
32
- subject { client.ref_data_isin(%w[WRONG12345], mapped: true) }
42
+ context 'with wrong ISIN', vcr: { cassette_name: 'ref-data/wrong_isin_mapped' } do
43
+ subject { client.ref_data_isin(%w[WRONG12345], mapped: true) }
33
44
 
34
- it 'returns nil value for given ISIN' do
35
- expect(subject).to eq('WRONG12345' => nil)
36
- end
45
+ it 'returns nil value for given ISIN' do
46
+ expect(subject).to eq('WRONG12345' => nil)
37
47
  end
38
48
  end
39
49
  end
@@ -2,12 +2,12 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
2
2
 
3
3
  require 'rubygems'
4
4
  require 'rspec'
5
+ require 'dotenv'
5
6
 
6
7
  require 'iex-ruby-client'
7
8
 
8
- Dir[File.join(File.dirname(__FILE__), 'support', '**/*.rb')].each do |file|
9
+ Dir[File.join(File.dirname(__FILE__), 'support', '**/*.rb')].sort.each do |file|
9
10
  require file
10
11
  end
11
12
 
12
- ENV['IEX_API_PUBLISHABLE_TOKEN'] ||= 'test-iex-api-publishable-token'
13
- ENV['IEX_API_SECRET_TOKEN'] ||= 'test-iex-api-secret-token'
13
+ Dotenv.load('.env', '.env.sample')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iex-ruby-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Doubrovkine
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-01 00:00:00.000000000 Z
11
+ date: 2020-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -142,6 +142,7 @@ executables: []
142
142
  extensions: []
143
143
  extra_rdoc_files: []
144
144
  files:
145
+ - ".env.sample"
145
146
  - ".github/FUNDING.yml"
146
147
  - ".gitignore"
147
148
  - ".rspec"
@@ -161,11 +162,13 @@ files:
161
162
  - lib/iex-ruby-client.rb
162
163
  - lib/iex/api.rb
163
164
  - lib/iex/api/client.rb
164
- - lib/iex/api/config.rb
165
+ - lib/iex/api/config/client.rb
166
+ - lib/iex/api/config/logger.rb
165
167
  - lib/iex/cloud.rb
166
168
  - lib/iex/cloud/connection.rb
167
169
  - lib/iex/cloud/request.rb
168
170
  - lib/iex/cloud/response.rb
171
+ - lib/iex/endpoints/advanced_stats.rb
169
172
  - lib/iex/endpoints/chart.rb
170
173
  - lib/iex/endpoints/company.rb
171
174
  - lib/iex/endpoints/crypto.rb
@@ -188,6 +191,7 @@ files:
188
191
  - lib/iex/errors/symbol_not_found_error.rb
189
192
  - lib/iex/logger.rb
190
193
  - lib/iex/resources.rb
194
+ - lib/iex/resources/advanced_stats.rb
191
195
  - lib/iex/resources/chart.rb
192
196
  - lib/iex/resources/company.rb
193
197
  - lib/iex/resources/crypto.rb
@@ -205,6 +209,8 @@ files:
205
209
  - lib/iex/resources/symbol.rb
206
210
  - lib/iex/resources/symbols.rb
207
211
  - lib/iex/version.rb
212
+ - spec/fixtures/iex/advanced_stats/invalid.yml
213
+ - spec/fixtures/iex/advanced_stats/msft.yml
208
214
  - spec/fixtures/iex/chart/1d.yml
209
215
  - spec/fixtures/iex/chart/20190306.yml
210
216
  - spec/fixtures/iex/chart/bad_option.yml
@@ -248,7 +254,9 @@ files:
248
254
  - spec/fixtures/iex/sectors/sectors-performance.yml
249
255
  - spec/fixtures/iex/stock_market/list_mostactive.yml
250
256
  - spec/iex/client_spec.rb
251
- - spec/iex/config_spec.rb
257
+ - spec/iex/config/client_spec.rb
258
+ - spec/iex/config/logger_spec.rb
259
+ - spec/iex/endpoints/advanced_stats_spec.rb
252
260
  - spec/iex/endpoints/chart_spec.rb
253
261
  - spec/iex/endpoints/company_spec.rb
254
262
  - spec/iex/endpoints/crypto_spec.rb
@@ -289,11 +297,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
289
297
  - !ruby/object:Gem::Version
290
298
  version: 1.3.6
291
299
  requirements: []
292
- rubygems_version: 3.1.3
300
+ rubygems_version: 3.1.4
293
301
  signing_key:
294
302
  specification_version: 4
295
303
  summary: IEX Finance API Ruby client with support for retrieving stock quotes.
296
304
  test_files:
305
+ - spec/fixtures/iex/advanced_stats/invalid.yml
306
+ - spec/fixtures/iex/advanced_stats/msft.yml
297
307
  - spec/fixtures/iex/chart/1d.yml
298
308
  - spec/fixtures/iex/chart/20190306.yml
299
309
  - spec/fixtures/iex/chart/bad_option.yml
@@ -337,7 +347,9 @@ test_files:
337
347
  - spec/fixtures/iex/sectors/sectors-performance.yml
338
348
  - spec/fixtures/iex/stock_market/list_mostactive.yml
339
349
  - spec/iex/client_spec.rb
340
- - spec/iex/config_spec.rb
350
+ - spec/iex/config/client_spec.rb
351
+ - spec/iex/config/logger_spec.rb
352
+ - spec/iex/endpoints/advanced_stats_spec.rb
341
353
  - spec/iex/endpoints/chart_spec.rb
342
354
  - spec/iex/endpoints/company_spec.rb
343
355
  - spec/iex/endpoints/crypto_spec.rb
@@ -1,47 +0,0 @@
1
- module IEX
2
- module Api
3
- module Config
4
- extend self
5
-
6
- ATTRIBUTES = %i[
7
- proxy
8
- user_agent
9
- ca_path
10
- ca_file
11
- logger
12
- timeout
13
- open_timeout
14
- endpoint
15
- publishable_token
16
- secret_token
17
- ].freeze
18
-
19
- attr_accessor(*Config::ATTRIBUTES)
20
-
21
- def reset!
22
- self.endpoint = 'https://cloud.iexapis.com/v1'
23
- self.publishable_token = ENV['IEX_API_PUBLISHABLE_TOKEN']
24
- self.secret_token = ENV['IEX_API_SECRET_TOKEN']
25
- self.user_agent = "IEX Ruby Client/#{IEX::VERSION}"
26
- self.ca_path = defined?(OpenSSL) ? OpenSSL::X509::DEFAULT_CERT_DIR : nil
27
- self.ca_file = defined?(OpenSSL) ? OpenSSL::X509::DEFAULT_CERT_FILE : nil
28
- self.proxy = nil
29
- self.logger = nil
30
- self.timeout = nil
31
- self.open_timeout = nil
32
- end
33
- end
34
-
35
- class << self
36
- def configure
37
- block_given? ? yield(Config) : Config
38
- end
39
-
40
- def config
41
- Config
42
- end
43
- end
44
- end
45
- end
46
-
47
- IEX::Api::Config.reset!
@@ -1,22 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe IEX::Api::Config do
4
- before do
5
- IEX::Api.config.reset!
6
- end
7
- describe '#defaults' do
8
- it 'sets endpoint' do
9
- expect(IEX::Api.config.endpoint).to eq 'https://cloud.iexapis.com/v1'
10
- end
11
- end
12
- describe '#configure' do
13
- before do
14
- IEX::Api.configure do |config|
15
- config.endpoint = 'updated'
16
- end
17
- end
18
- it 'sets endpoint' do
19
- expect(IEX::Api.config.endpoint).to eq 'updated'
20
- end
21
- end
22
- end