bls_api 1.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9f4938be36f9e555cb7ba3e8c68114143c5d06ae
4
+ data.tar.gz: cdf087da4ddc16b5c5d66d7fb380ea6739ab69a7
5
+ SHA512:
6
+ metadata.gz: fd0c6a16df4d39cd383cd722a698dd8e25e1310cff4756c84f457a6e59b848cc726dfac422c1b7b28b7568b470ab2bcf7143acfc4770a0818cef078055264158
7
+ data.tar.gz: 07613e657478815be83623498283d6d5ac87a50161fb59e618e42f294509cbe3cee563d62241cbc40bb039d5d08bd65e481bd0e1aaf02acff4f8df7a7cae4f46
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ - 2.2.3
5
+ - 1.9.3-p448
6
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bls_api.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 The Associated Press
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Bureau of Labor Statistics API wrapper #
2
+
3
+ This is a Ruby wrapper for [v2][v2] of the [BLS Public Data API][bls-dev].
4
+
5
+ [bls-dev]: http://www.bls.gov/developers/home.htm
6
+ [v2]: http://www.bls.gov/developers/api_signature_v2.htm
7
+
8
+ ## Installation ##
9
+
10
+ $ gem install bls_api
11
+
12
+ ## Usage ##
13
+
14
+ >> require "bls_api"
15
+ >> client = BLS_API::Client.new
16
+ >> data = client.get(:series_ids => ["LNS14000000"], :start_year => 2015, :end_year => 2015)
17
+
18
+ You'll get back a Hash, with the series IDs you provided as keys. The values
19
+ are `BLS_API::Series` instances, which expose the data for each series along
20
+ with some BLS-provided metadata:
21
+
22
+ >> unemployment_rate = data["LNS14000000"]
23
+
24
+ >> unemployment_rate.catalog.series_title
25
+ => "(Seas) Unemployment Rate"
26
+ >> unemployment_rate.catalog.seasonality
27
+ => "Seasonally Adjusted"
28
+
29
+ >> june = unemployment_rate.get_month(2015, 6)
30
+ >> june.value.to_f
31
+ => 5.3
32
+
33
+ ### Series metadata ###
34
+
35
+ `Series#catalog` just returns an [OpenStruct][openstruct] with whatever
36
+ metadata the BLS API returned for that series:
37
+
38
+ >> unemployment_rate.catalog
39
+ => #<OpenStruct series_title="(Seas) Unemployment Rate",
40
+ series_id="LNS14000000", seasonality="Seasonally Adjusted",
41
+ survey_name="Labor Force Statistics from the Current Population Survey",
42
+ measure_data_type="Percent or rate", commerce_industry="All Industries",
43
+ occupation="All Occupations", cps_labor_force_status="Unemployment rate",
44
+ demographic_age="16 years and over",
45
+ demographic_ethnic_origin="All Origins", demographic_race="All Races",
46
+ demographic_gender="Both Sexes",
47
+ demographic_marital_status="All marital statuses",
48
+ demographic_education="All educational levels">
49
+
50
+ This is great if you already know what fields to expect; otherwise the more
51
+ familiar way to explore it might be as a Hash:
52
+
53
+ >> unemployment_rate.catalog.to_h.keys
54
+ => [:series_title, :series_id, :seasonality, :survey_name,
55
+ :measure_data_type, :commerce_industry, :occupation,
56
+ :cps_labor_force_status, :demographic_age, :demographic_ethnic_origin,
57
+ :demographic_race, :demographic_gender, :demographic_marital_status,
58
+ :demographic_education]
59
+
60
+ [openstruct]: http://ruby-doc.org/stdlib-2.3.0/libdoc/ostruct/rdoc/OpenStruct.html
61
+
62
+ ### Data by month ###
63
+
64
+ You've already seen `Series#get_month`, which takes a year and month
65
+ (1 = January) and returns a `BLS_API::Month`, and you've seen `Month#value`:
66
+
67
+ >> june = unemployment_rate.get_month(2015, 6)
68
+ >> june.value.to_f
69
+ => 5.3
70
+
71
+ You also can get one-, three-, six- or 12-month changes (net changes _or_
72
+ percent changes):
73
+
74
+ >> june.net_change(1).to_f
75
+ => -0.2
76
+ >> june.percent_change(1).to_f
77
+ => -3.6
78
+
79
+ These examples use `#to_f` for display purposes because this client uses
80
+ [BigDecimal][bigdecimal] instances by default to preserve precision:
81
+
82
+ >> june.value
83
+ => #<BigDecimal:7ff5493f5390,'0.53E1',18(18)>
84
+
85
+ If you'd prefer Floats, you can set `client.use_floats = true` before making
86
+ your request:
87
+
88
+ >> client.use_floats = true
89
+ >> data = client.get(:series_ids => ["LNS14000000"], :start_year => 2015, :end_year => 2015)
90
+ >> unemployment_rate = data["LNS14000000"]
91
+ >> unemployment_rate.get_month(2015, 6).value
92
+ => 5.3
93
+
94
+ Also, BLS sometimes provides footnotes for certain data points, which you can
95
+ access via `Month#footnotes`:
96
+
97
+ >> last_month.footnotes
98
+ => {"P"=>"Preliminary"}
99
+
100
+ [bigdecimal]: http://ruby-doc.org/stdlib-2.3.0/libdoc/bigdecimal/rdoc/BigDecimal.html
101
+
102
+ ## Configuration ##
103
+
104
+ You'll need an [API key][api-key], which you can provide as an argument to
105
+ `BLS_API::Client.new` or as a `BLS_API_KEY` environment variable. (If you
106
+ provide both, the argument to `Client.new` takes precedence.)
107
+
108
+ [api-key]: http://data.bls.gov/registrationEngine/
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "bls_api"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/bls_api.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "bls_api/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bls_api"
8
+ spec.version = BLS_API::VERSION
9
+ spec.authors = ["Justin Myers"]
10
+ spec.email = ["jmyers@ap.org"]
11
+ spec.license = "MIT"
12
+
13
+ spec.summary = %q{API wrapper for data from the U.S. Bureau of Labor Statistics.}
14
+ spec.description = %q{API wrapper for data from the U.S. Bureau of Labor Statistics.}
15
+ spec.homepage = "https://github.com/associatedpress/bls_api"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.10"
23
+ spec.add_development_dependency "climate_control", "~> 0.0.3"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "webmock", "~> 1.21"
27
+ end
@@ -0,0 +1,80 @@
1
+ require "bls_api/constants"
2
+ require "bls_api/destringify"
3
+ require "bls_api/errors"
4
+ require "bls_api/raw_request"
5
+
6
+ module BLS_API
7
+ class Client
8
+ include BLS_API::Destringify
9
+ include BLS_API::RawRequest
10
+
11
+ attr_accessor :api_key
12
+ attr_accessor :request_annual_averages
13
+ attr_accessor :request_catalog
14
+ attr_accessor :request_calculations
15
+ attr_accessor :use_floats
16
+
17
+ def initialize(api_key = nil)
18
+ @api_key = ENV.fetch("BLS_API_KEY", nil)
19
+ @api_key = api_key unless api_key.nil?
20
+ @api_key = nil if @api_key.is_a?(String) && @api_key.empty?
21
+ if @api_key.nil?
22
+ missing_key_message = <<-EOF.gsub(/^ */, "").gsub(/\r?\n/, " ").strip
23
+ You must provide an API key as an argument to BLS_API::Client.new or
24
+ as the BLS_API_KEY environment variable. If you do not have an API
25
+ key, register for one at http://data.bls.gov/registrationEngine/.
26
+ EOF
27
+ raise BLS_API::Errors::ConfigurationError, missing_key_message
28
+ end
29
+
30
+ @request_annual_averages = true
31
+ @request_catalog = true
32
+ @request_calculations = true
33
+ @use_floats = false
34
+ end
35
+
36
+ # Public: Request a batch of data from the BLS API.
37
+ #
38
+ # By default, raises BLS_API::Errors::APIError if the request is
39
+ # unsuccessful. (You can catch this with an IOError, if that's more your
40
+ # thing.)
41
+ #
42
+ # options - A Hash with three required arguments and four optional
43
+ # arguments.
44
+ # Required arguments include:
45
+ # :series_ids - An Array of String series IDs for which to
46
+ # request data. If a String is provided instead, it
47
+ # is assumed to be a single series ID.
48
+ # :start_year - An Integer representing the earliest year for
49
+ # which to request data.
50
+ # :end_year - An Integer representing the latest year for which
51
+ # to request data. Note that the BLS API will
52
+ # return an error if you specify a year for which
53
+ # no data exists; for example, an :end_year of 2016
54
+ # will raise an error during January of 2016 when
55
+ # no 2016 data has yet been released.
56
+ # Optional arguments include:
57
+ # :catch_errors - A Boolean specifying whether to raise an
58
+ # APIError if the request is unsuccessful
59
+ # (default: true).
60
+ # :catalog - A Boolean specifying whether to include
61
+ # catalog data in the response
62
+ # (default: true).
63
+ # :calculations - A Boolean specifying whether to include
64
+ # net-change and percent-change calculations
65
+ # in the response (default: true).
66
+ # :annual_averages - A Boolean specifying whether to include
67
+ # annual averages in the response
68
+ # (default: true).
69
+ #
70
+ # Returns a Hash with the given String series IDs as keys and
71
+ # BLS_API::Series instances as values.
72
+ def get(options = {})
73
+ raw_response = self.make_api_request(options)
74
+ destringified = self.destringify(raw_response, @use_floats)
75
+ series = Hash[destringified["Results"]["series"].map do |raw_series|
76
+ [raw_series["seriesID"], BLS_API::Series.new(raw_series)]
77
+ end]
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,11 @@
1
+ module BLS_API
2
+ module Constants
3
+ ENDPOINT_URL = "http://api.bls.gov/publicAPI/v2/timeseries/data/"
4
+
5
+ MAX_SERIES_PER_REQUEST = 50
6
+ MAX_YEARS_PER_REQUEST = 20
7
+
8
+ MAX_RETRIES = 3
9
+ TIME_BETWEEN_RETRIES = 5 # In seconds
10
+ end
11
+ end
@@ -0,0 +1,124 @@
1
+ require "bigdecimal"
2
+
3
+ module BLS_API
4
+ module Destringify
5
+ # Public: Convert numeric data in a BLS API response from Strings to
6
+ # instances of more useful Numeric subclasses for the user's convenience.
7
+ #
8
+ # (My guess is BLS sends data as strings in order to maintain precision and
9
+ # simplify the JSON conversion on their end.)
10
+ #
11
+ # raw_response - A Hash of parsed JSON data from an API response, such as
12
+ # that returned by BLS_API::RawRequest#make_api_request.
13
+ # use_floats - An optional Boolean specifying whether to express values
14
+ # as Floats (for simplicity) instead of as BigDecimals
15
+ # (for precision) (default: false).
16
+ #
17
+ # Returns a Hash similar to raw_response but with some keys and values
18
+ # converted to Numerics.
19
+ def destringify(raw_response, use_floats = false)
20
+ output = {}
21
+
22
+ raw_response.keys.reject { |key| key == "Results" }.each do |key|
23
+ output[key] = raw_response[key]
24
+ end
25
+
26
+ output_results = {}
27
+ output["Results"] = output_results
28
+ raw_results = raw_response["Results"]
29
+ raw_results.keys.reject { |key| key == "series" }.each do |key|
30
+ output_results[key] = raw_results[key]
31
+ end
32
+
33
+ output_results["series"] = raw_results["series"].map do |raw_series|
34
+ self.destringify_series(raw_series, use_floats)
35
+ end
36
+
37
+ output
38
+ end
39
+
40
+ # Internal: Convert the keys and values in a calculations object (change in
41
+ # an indicator over the past {1,3,6,12} months) from Strings to Numerics.
42
+ #
43
+ # raw_calcs - A Hash with String keys (specifying the number of months
44
+ # over which a given change was calculated) and String values
45
+ # (specifying the change over that period of time).
46
+ # use_floats - An optional Boolean specifying whether to express values
47
+ # as Floats (for simplicity) instead of as BigDecimals
48
+ # (for precision) (default: false).
49
+ #
50
+ # Returns a Hash with Integer keys and BigDecimal values (to preserve
51
+ # precision).
52
+ def destringify_calculations(raw_calcs, use_floats = false)
53
+ Hash[raw_calcs.each_pair.map do |key, value|
54
+ [
55
+ key.to_i,
56
+
57
+ case use_floats
58
+ when true then value.to_f
59
+ else BigDecimal.new(value)
60
+ end
61
+ ]
62
+ end]
63
+ end
64
+
65
+ # Internal: Convert all quantitative keys and values in a month object
66
+ # (statistics and optional changes corresponding to a particular
67
+ # series/month combination) from Strings to Numerics.
68
+ #
69
+ # raw_month - A Hash for a given month's data point. Should contain
70
+ # "year", "period" and "value" properties, at least.
71
+ # use_floats - An optional Boolean specifying whether to express values
72
+ # as Floats (for simplicity) instead of as BigDecimals
73
+ # (for precision) (default: false).
74
+ #
75
+ # Returns a Hash with all of the same keys as `raw_month`.
76
+ def destringify_month(raw_month, use_floats = false)
77
+ output = {}
78
+
79
+ output["year"] = raw_month["year"].to_i
80
+ output["value"] = case use_floats
81
+ when true then raw_month["value"].to_f
82
+ else BigDecimal.new(raw_month["value"])
83
+ end
84
+
85
+ if raw_month.include?("calculations")
86
+ output["calculations"] = Hash[
87
+ raw_month["calculations"].each_pair.map do |name, calcs|
88
+ [name, self.destringify_calculations(calcs, use_floats)]
89
+ end
90
+ ]
91
+ end
92
+
93
+ keys_to_pass_through = raw_month.keys.reject do |key|
94
+ ["year", "value", "calculations"].include?(key)
95
+ end
96
+ keys_to_pass_through.each { |key| output[key] = raw_month[key] }
97
+
98
+ output
99
+ end
100
+
101
+ # Internal: Convert numeric data in a BLS API series from Strings to
102
+ # Numerics.
103
+ #
104
+ # raw_series - An Array of month Hashes from a BLS API response.
105
+ # use_floats - An optional Boolean specifying whether to express values
106
+ # as Floats (for simplicity) instead of as BigDecimals
107
+ # (for precision) (default: false).
108
+ #
109
+ # Returns an Array of month Hashes after conversion by #destringify_month.
110
+ def destringify_series(raw_series, use_floats = false)
111
+ output = {}
112
+
113
+ raw_series.keys.reject { |key| key == "data" }.each do |key|
114
+ output[key] = raw_series[key]
115
+ end
116
+
117
+ output["data"] = raw_series["data"].map do |raw_month|
118
+ self.destringify_month(raw_month, use_floats)
119
+ end
120
+
121
+ output
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,8 @@
1
+ module BLS_API
2
+ module Errors
3
+ class APIError < IOError; end
4
+ class ConfigurationError < ArgumentError; end
5
+ class NotRetrievedError < KeyError; end
6
+ class OptionsError < ArgumentError; end
7
+ end
8
+ end
@@ -0,0 +1,63 @@
1
+ require "bls_api/errors"
2
+
3
+ module BLS_API
4
+ class Month
5
+ def initialize(raw_month)
6
+ @raw_month = raw_month
7
+ end
8
+
9
+ def year
10
+ @raw_month["year"]
11
+ end
12
+
13
+ def month
14
+ @raw_month["period"].slice(/^M(\d+)$/, 1).to_i
15
+ end
16
+
17
+ def value
18
+ @raw_month["value"]
19
+ end
20
+
21
+ def footnotes
22
+ non_empty_footnotes = @raw_month["footnotes"].reject { |x| x.empty? }
23
+ Hash[non_empty_footnotes.map do |footnote|
24
+ [footnote["code"], footnote["text"]]
25
+ end]
26
+ end
27
+
28
+ def net_change(timeframe)
29
+ unless @raw_month.include?("calculations")
30
+ raise BLS_API::NotRetrievedError, "Calculations not retrieved"
31
+ end
32
+ net_changes = @raw_month["calculations"].fetch("net_changes") do
33
+ raise BLS_API::NotRetrievedError, (
34
+ "Net-change calculations not available from BLS")
35
+ end
36
+ net_changes.fetch(timeframe) do
37
+ raise BLS_API::NotRetrievedError, (
38
+ "#{timeframe}-month net changes not available from BLS")
39
+ end
40
+ end
41
+
42
+ def percent_change(timeframe)
43
+ unless @raw_month.include?("calculations")
44
+ raise BLS_API::NotRetrievedError, "Calculations not retrieved"
45
+ end
46
+ percent_changes = @raw_month["calculations"].fetch("pct_changes") do
47
+ raise BLS_API::NotRetrievedError, (
48
+ "Percent-change calculations not available from BLS")
49
+ end
50
+ percent_changes.fetch(timeframe) do
51
+ raise BLS_API::NotRetrievedError, (
52
+ "#{timeframe}-month percent changes not available from BLS")
53
+ end
54
+ end
55
+
56
+ def <=>(other)
57
+ year_comparison = self.year <=> other.year
58
+ return year_comparison unless year_comparison == 0
59
+
60
+ month_comparison = self.month <=> other.month
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,89 @@
1
+ require "json"
2
+
3
+ require "bls_api/constants"
4
+ require "bls_api/errors"
5
+
6
+ module BLS_API
7
+ module RawRequest
8
+ # Internal: Request a batch of data from the BLS API.
9
+ #
10
+ # By default, raises BLS_API::Errors::APIError if the request is
11
+ # unsuccessful. (You can catch this with an IOError, if that's more your
12
+ # thing.)
13
+ #
14
+ # options - A Hash with three required arguments and four optional
15
+ # arguments.
16
+ # Required arguments include:
17
+ # :series_ids - An Array of String series IDs for which to
18
+ # request data. If a String is provided instead, it
19
+ # is assumed to be a single series ID.
20
+ # :start_year - An Integer representing the earliest year for
21
+ # which to request data.
22
+ # :end_year - An Integer representing the latest year for which
23
+ # to request data. Note that the BLS API will
24
+ # return an error if you specify a year for which
25
+ # no data exists; for example, an :end_year of 2016
26
+ # will raise an error during January of 2016 when
27
+ # no 2016 data has yet been released.
28
+ # Optional arguments include:
29
+ # :catch_errors - A Boolean specifying whether to raise an
30
+ # APIError if the request is unsuccessful
31
+ # (default: true).
32
+ # :catalog - A Boolean specifying whether to include
33
+ # catalog data in the response
34
+ # (default: true).
35
+ # :calculations - A Boolean specifying whether to include
36
+ # net-change and percent-change calculations
37
+ # in the response (default: true).
38
+ # :annual_averages - A Boolean specifying whether to include
39
+ # annual averages in the response
40
+ # (default: true).
41
+ #
42
+ # Returns a Hash of the parsed JSON response from the API.
43
+ def make_api_request(options = {})
44
+ # Ensure required arguments are provided.
45
+ series_ids = options.fetch(:series_ids) do
46
+ raise BLS_API::Errors::OptionsError, "Missing series IDs"
47
+ end
48
+ series_ids = [series_ids] if series_ids.is_a?(String)
49
+ start_year = options.fetch(:start_year) do
50
+ raise BLS_API::Errors::OptionsError, "Missing start year"
51
+ end
52
+ end_year = options.fetch(:end_year) do
53
+ raise BLS_API::Errors::OptionsError, "Missing end year"
54
+ end
55
+
56
+ # Build and make the API request.
57
+ endpoint_uri = URI.parse(BLS_API::Constants::ENDPOINT_URL)
58
+ req = Net::HTTP::Post.new(endpoint_uri.path)
59
+ req.body = {
60
+ "seriesid" => series_ids,
61
+ "startyear" => start_year.to_s,
62
+ "endyear" => end_year.to_s,
63
+ "annualaverage" => options.fetch(:annual_averages, true),
64
+ "calculations" => options.fetch(:calculations, true),
65
+ "catalog" => options.fetch(:catalog, true),
66
+ "registrationKey" => self.api_key
67
+ }.to_json
68
+ req.content_type = "application/json"
69
+ res = Net::HTTP.start(endpoint_uri.host, endpoint_uri.port) do |http|
70
+ http.request(req)
71
+ end
72
+
73
+ # Let the user know if the API request failed.
74
+ parsed = JSON.parse(res.body)
75
+ catch_errors = options.fetch(:catch_errors, true)
76
+ if catch_errors && parsed["status"] != "REQUEST_SUCCEEDED"
77
+ error_messages = %Q{"#{parsed["message"].join(%Q{", "})}"}
78
+ error_message = <<-EOF.gsub(/^ */, "").gsub(/\r?\n/, " ").strip
79
+ BLS API returned an error: #{parsed["status"]}
80
+ Additional messages were: #{error_messages}
81
+ EOF
82
+ raise BLS_API::Errors::APIError, error_message
83
+ end
84
+
85
+ # Return the whole API response.
86
+ parsed
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,56 @@
1
+ require "ostruct"
2
+
3
+ require "bls_api/month"
4
+
5
+ module BLS_API
6
+ class Series
7
+ include BLS_API::Destringify
8
+
9
+ attr_reader :id
10
+
11
+ def initialize(raw_series)
12
+ @id = raw_series["seriesID"]
13
+ begin
14
+ @raw_series = self.destringify_series(raw_series)
15
+ rescue ArgumentError
16
+ # Series was already destringified _and_ it was using Floats.
17
+ @raw_series = raw_series
18
+ end
19
+ end
20
+
21
+ # Public: Return catalog information for this series if available.
22
+ #
23
+ # Returns an OpenStruct if possible; returns nil if no catalog information
24
+ # was received.
25
+ def catalog
26
+ return nil unless @raw_series.include?("catalog")
27
+ return @catalog unless @catalog.nil?
28
+ @catalog = OpenStruct.new(@raw_series["catalog"])
29
+ end
30
+
31
+ # Public: Return information for the given month.
32
+ #
33
+ # year - An Integer representing the year for which to retrieve data.
34
+ # month - An Integer representing the month (1 = January) for which to
35
+ # retrieve data.
36
+ #
37
+ # Returns a BLS_API::Month.
38
+ def get_month(year, month)
39
+ self.monthly # Ensure we've converted all months.
40
+ @months.detect do |parsed_month|
41
+ parsed_month.year == year && parsed_month.month == month
42
+ end
43
+ end
44
+
45
+ # Public: Return information for all months.
46
+ #
47
+ # Returns an Array of BLS_API::Months.
48
+ def monthly
49
+ return @months unless @months.nil?
50
+ @months = @raw_series["data"].map do |month_data|
51
+ BLS_API::Month.new(month_data)
52
+ end
53
+ @months.sort!.reverse!
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ module BLS_API
2
+ VERSION = "1.0.1"
3
+ end
data/lib/bls_api.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "bls_api/client"
2
+ require "bls_api/constants"
3
+ require "bls_api/destringify"
4
+ require "bls_api/errors"
5
+ require "bls_api/month"
6
+ require "bls_api/raw_request"
7
+ require "bls_api/series"
8
+ require "bls_api/version"
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bls_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Myers
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: climate_control
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.3
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.21'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.21'
83
+ description: API wrapper for data from the U.S. Bureau of Labor Statistics.
84
+ email:
85
+ - jmyers@ap.org
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - LICENSE
95
+ - README.md
96
+ - Rakefile
97
+ - bin/console
98
+ - bin/setup
99
+ - bls_api.gemspec
100
+ - lib/bls_api.rb
101
+ - lib/bls_api/client.rb
102
+ - lib/bls_api/constants.rb
103
+ - lib/bls_api/destringify.rb
104
+ - lib/bls_api/errors.rb
105
+ - lib/bls_api/month.rb
106
+ - lib/bls_api/raw_request.rb
107
+ - lib/bls_api/series.rb
108
+ - lib/bls_api/version.rb
109
+ homepage: https://github.com/associatedpress/bls_api
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.4.5.1
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: API wrapper for data from the U.S. Bureau of Labor Statistics.
133
+ test_files: []