yt 0.5.12 → 0.5.13
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/Gemfile.lock +1 -1
- data/HISTORY.md +1 -0
- data/README.md +1 -1
- data/lib/yt/collections/earnings.rb +1 -21
- data/lib/yt/models/request.rb +40 -13
- data/lib/yt/version.rb +1 -1
- data/spec/associations/device_auth/partnered_channels_spec.rb +2 -1
- data/spec/collections/earnings_spec.rb +0 -40
- data/spec/models/request_spec.rb +62 -19
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc79b5682be98448f4c2ad0e47e025ece396f2ca
|
4
|
+
data.tar.gz: 3b3ba9a7646c1fe350c10bf1cc01ffc9e3c01062
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8be6da305362d7f2578157ba5cdd0d6c717bbbd8f5a677ac898af79332fc5bb098b0f777006fc7d002da696346403910d724c673e308d897b1c700fa83e295e
|
7
|
+
data.tar.gz: 90842585d61ad708d4dba3fefac8b7bc2f51ac8eeae58ca3acdd5d989bf4eae495ba9a6b984b758f4e7bc5f2b20a5d8f14604c6e821ecb935745af67fb5363c8
|
data/Gemfile.lock
CHANGED
data/HISTORY.md
CHANGED
@@ -19,6 +19,7 @@ v0.5 - 2014/05/16
|
|
19
19
|
* New ContentOwner subclass of Account with access to partnered channels
|
20
20
|
* Automatically refresh the access token when it expires or becomes invalid
|
21
21
|
* Retry once YouTube earning queries that return error 503
|
22
|
+
* Wait 3 seconds and retry *every* request that returns 500, 503 or 400 with "Invalid query"
|
22
23
|
|
23
24
|
v0.4 - 2014/05/09
|
24
25
|
--------------------
|
data/README.md
CHANGED
@@ -365,7 +365,7 @@ To install on your system, run
|
|
365
365
|
|
366
366
|
To use inside a bundled Ruby project, add this line to the Gemfile:
|
367
367
|
|
368
|
-
gem 'yt', '~> 0.5.
|
368
|
+
gem 'yt', '~> 0.5.13'
|
369
369
|
|
370
370
|
Since the gem follows [Semantic Versioning](http://semver.org),
|
371
371
|
indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
|
@@ -4,17 +4,9 @@ module Yt
|
|
4
4
|
module Collections
|
5
5
|
class Earnings < Base
|
6
6
|
|
7
|
-
def within(days_range
|
7
|
+
def within(days_range)
|
8
8
|
@days_range = days_range
|
9
9
|
Hash[*flat_map{|daily_earning| daily_earning}]
|
10
|
-
# NOTE: Once in a while, YouTube responds with 400 Error and the message
|
11
|
-
# "Invalid query. Query did not conform to the expectations."; in this
|
12
|
-
# case running the same query after one second fixes the issue. This is
|
13
|
-
# not documented by YouTube and hardly testable, but trying again the
|
14
|
-
# same query is a workaround that works and can hardly cause any damage.
|
15
|
-
# Similarly, once in while YouTube responds with a random 503 error.
|
16
|
-
rescue Yt::Error => e
|
17
|
-
try_again && rescue?(e) ? sleep(3) && within(days_range, false) : raise
|
18
10
|
end
|
19
11
|
|
20
12
|
private
|
@@ -41,18 +33,6 @@ module Yt
|
|
41
33
|
def items_key
|
42
34
|
'rows'
|
43
35
|
end
|
44
|
-
|
45
|
-
def rescue?(error)
|
46
|
-
bad_request?(error) || backend_error?(error)
|
47
|
-
end
|
48
|
-
|
49
|
-
def bad_request?(error)
|
50
|
-
'badRequest'.in?(error.reasons) && error.message =~ /did not conform/
|
51
|
-
end
|
52
|
-
|
53
|
-
def backend_error?(error)
|
54
|
-
'backendError'.in?(error.reasons)
|
55
|
-
end
|
56
36
|
end
|
57
37
|
end
|
58
38
|
end
|
data/lib/yt/models/request.rb
CHANGED
@@ -28,25 +28,13 @@ module Yt
|
|
28
28
|
def run
|
29
29
|
if response.is_a? @expected_response
|
30
30
|
response.tap{|response| response.body = parse_format response.body}
|
31
|
-
elsif run_again_with_refreshed_authentication?
|
32
|
-
run
|
33
31
|
else
|
34
|
-
raise
|
32
|
+
run_again? ? run : raise(error_for(response), request_error_message)
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
38
36
|
private
|
39
37
|
|
40
|
-
# If a request authorized with an access token returns 401, then the
|
41
|
-
# access token might have expired. If a refresh token is also present,
|
42
|
-
# try to run the request one more time with a refreshed access token.
|
43
|
-
def run_again_with_refreshed_authentication?
|
44
|
-
if response.is_a? Net::HTTPUnauthorized
|
45
|
-
@response = @http_request = @uri = nil
|
46
|
-
@auth.refresh
|
47
|
-
end if @auth.respond_to? :refresh
|
48
|
-
end
|
49
|
-
|
50
38
|
def response
|
51
39
|
@response ||= Net::HTTP.start(*net_http_options) do |http|
|
52
40
|
http.request http_request
|
@@ -113,6 +101,45 @@ module Yt
|
|
113
101
|
end if body
|
114
102
|
end
|
115
103
|
|
104
|
+
# There are two cases to run a request again: YouTube responds with a
|
105
|
+
# random error that can be fixed by waiting for some seconds and running
|
106
|
+
# the exact same query, or the access token needs to be refreshed.
|
107
|
+
def run_again?
|
108
|
+
run_again_with_refreshed_authentication? || run_again_after_a_while?
|
109
|
+
end
|
110
|
+
|
111
|
+
# Once in a while, YouTube responds with 500, or 503, or 400 Error and
|
112
|
+
# the text "Invalid query. Query did not conform to the expectations.".
|
113
|
+
# In all these cases, running the same query after some seconds fixes
|
114
|
+
# the issue. This it not documented by YouTube and hardly testable, but
|
115
|
+
# trying again is a workaround that works and hardly causes any damage.
|
116
|
+
def run_again_after_a_while?(max_retries = 1)
|
117
|
+
@retries_so_far ||= -1
|
118
|
+
@retries_so_far += 1
|
119
|
+
if (@retries_so_far < max_retries) && worth_another_try?
|
120
|
+
@response = @http_request = @uri = nil
|
121
|
+
sleep 3
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def worth_another_try?
|
126
|
+
case response
|
127
|
+
when Net::HTTPServerError then true
|
128
|
+
when Net::HTTPBadRequest then response.body =~ /did not conform/
|
129
|
+
else false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# If a request authorized with an access token returns 401, then the
|
134
|
+
# access token might have expired. If a refresh token is also present,
|
135
|
+
# try to run the request one more time with a refreshed access token.
|
136
|
+
def run_again_with_refreshed_authentication?
|
137
|
+
if response.is_a? Net::HTTPUnauthorized
|
138
|
+
@response = @http_request = @uri = nil
|
139
|
+
@auth.refresh
|
140
|
+
end if @auth.respond_to? :refresh
|
141
|
+
end
|
142
|
+
|
116
143
|
def error_for(response)
|
117
144
|
case response
|
118
145
|
when Net::HTTPServerError then Errors::ServerError
|
data/lib/yt/version.rb
CHANGED
@@ -7,7 +7,8 @@ describe Yt::Associations::PartneredChannels, :partner do
|
|
7
7
|
context 'given a content owner with partnered channels' do
|
8
8
|
let(:content_owner) { $content_owner }
|
9
9
|
|
10
|
-
|
10
|
+
# NOTE: Uncomment once size does not runs through *all* the pages
|
11
|
+
# it { expect(partnered_channels.size).to be > 0 }
|
11
12
|
it { expect(partnered_channels.first).to be_a Yt::Channel }
|
12
13
|
end
|
13
14
|
end
|
@@ -4,10 +4,8 @@ require 'yt/collections/earnings'
|
|
4
4
|
describe Yt::Collections::Earnings do
|
5
5
|
subject(:collection) { Yt::Collections::Earnings.new parent: channel }
|
6
6
|
let(:channel) { Yt::Channel.new id: 'UCxO1tY8h1AhOz0T4ENwmpow' }
|
7
|
-
let(:msg) { {response_body: {error: {errors: [error]}}}.to_json }
|
8
7
|
let(:date) { 1.day.ago.to_date }
|
9
8
|
let(:dollars) { 10 }
|
10
|
-
let(:message) { 'Invalid query. Query did not conform to the expectations.' }
|
11
9
|
before { expect(collection).to behave }
|
12
10
|
|
13
11
|
describe '#within' do
|
@@ -16,43 +14,5 @@ describe Yt::Collections::Earnings do
|
|
16
14
|
|
17
15
|
it { expect(collection.within(date..date)[date]).to eq dollars }
|
18
16
|
end
|
19
|
-
|
20
|
-
# NOTE: This test is just a reflection of YouTube irrational behavior
|
21
|
-
# of raising 400 or 504 error once in a while when retrieving earnings.
|
22
|
-
# Hopefully this will get fixed and this code (and test) removed.
|
23
|
-
context 'given YouTube responds to the first request with' do
|
24
|
-
let(:behave) { receive(:flat_map) do
|
25
|
-
expect(collection).to receive(:flat_map).and_return [date, dollars]
|
26
|
-
raise Yt::Error, msg
|
27
|
-
end}
|
28
|
-
|
29
|
-
context 'an Invalid Query error' do
|
30
|
-
let(:error) { {reason: 'badRequest', message: message} }
|
31
|
-
|
32
|
-
it { expect(collection.within(date..date)[date]).to eq dollars }
|
33
|
-
end
|
34
|
-
|
35
|
-
context 'a Backend error' do
|
36
|
-
let(:error) { {reason: 'backendError', message: 'Backend Error'} }
|
37
|
-
|
38
|
-
it { expect(collection.within(date..date)[date]).to eq dollars }
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
context 'given YouTube responds to the second request with' do
|
43
|
-
let(:behave) { receive(:flat_map).twice.and_raise Yt::Error, msg }
|
44
|
-
|
45
|
-
context 'an Invalid Query error' do
|
46
|
-
let(:error) { {reason: 'badRequest', message: message} }
|
47
|
-
|
48
|
-
it { expect{collection.within date..date}.to raise_error Yt::Error }
|
49
|
-
end
|
50
|
-
|
51
|
-
context 'a Backend error' do
|
52
|
-
let(:error) { {reason: 'backendError', message: 'Backend Error'} }
|
53
|
-
|
54
|
-
it { expect{collection.within date..date}.to raise_error Yt::Error }
|
55
|
-
end
|
56
|
-
end
|
57
17
|
end
|
58
18
|
end
|
data/spec/models/request_spec.rb
CHANGED
@@ -1,31 +1,74 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'yt/models/request'
|
3
3
|
|
4
|
+
|
4
5
|
describe Yt::Request do
|
5
|
-
subject(:request) { Yt::Request.new
|
6
|
-
let(:
|
7
|
-
|
8
|
-
before { allow(response).to receive(:body) }
|
6
|
+
subject(:request) { Yt::Request.new host: 'example.com' }
|
7
|
+
let(:response) { response_class.new nil, nil, nil }
|
8
|
+
let(:response_body) { }
|
9
|
+
before { allow(response).to receive(:body).and_return response_body }
|
10
|
+
before { expect(Net::HTTP).to receive(:start).once.and_return response }
|
9
11
|
|
10
12
|
describe '#run' do
|
11
|
-
context 'given a request that returns
|
12
|
-
|
13
|
-
|
14
|
-
end
|
13
|
+
context 'given a request that returns' do
|
14
|
+
context 'a success code 2XX' do
|
15
|
+
let(:response_class) { Net::HTTPOK }
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
it { expect{request.run}.to fail }
|
19
|
-
end
|
17
|
+
it { expect{request.run}.not_to fail }
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
context 'an error code 5XX' do
|
21
|
+
let(:response_class) { Net::HTTPServerError }
|
22
|
+
let(:retry_response) { retry_response_class.new nil, nil, nil }
|
23
|
+
before { allow(retry_response).to receive(:body) }
|
24
|
+
before { expect(Net::HTTP).to receive(:start).at_least(:once).and_return retry_response }
|
25
|
+
|
26
|
+
context 'every time' do
|
27
|
+
let(:retry_response_class) { Net::HTTPServerError }
|
28
|
+
|
29
|
+
it { expect{request.run}.to fail }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'but returns a success code 2XX the second time' do
|
33
|
+
let(:retry_response_class) { Net::HTTPOK }
|
34
|
+
|
35
|
+
it { expect{request.run}.not_to fail }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'an error code 400 with "Invalid Query" message' do
|
40
|
+
let(:response_class) { Net::HTTPBadRequest }
|
41
|
+
let(:response_body) { {error: {errors: [message: message]}}.to_json }
|
42
|
+
let(:message) { 'Invalid query. Query did not conform to the expectations' }
|
43
|
+
|
44
|
+
let(:retry_response) { retry_response_class.new nil, nil, nil }
|
45
|
+
before { allow(retry_response).to receive(:body) }
|
46
|
+
before { expect(Net::HTTP).to receive(:start).at_least(:once).and_return retry_response }
|
47
|
+
|
48
|
+
context 'every time' do
|
49
|
+
let(:retry_response_class) { Net::HTTPBadRequest }
|
50
|
+
|
51
|
+
it { expect{request.run}.to fail }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'but returns a success code 2XX the second time' do
|
55
|
+
let(:retry_response_class) { Net::HTTPOK }
|
56
|
+
|
57
|
+
it { expect{request.run}.not_to fail }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'an error code 401' do
|
62
|
+
let(:response_class) { Net::HTTPUnauthorized }
|
63
|
+
|
64
|
+
it { expect{request.run}.to fail }
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'any other non-2XX error code' do
|
68
|
+
let(:response_class) { Net::HTTPNotFound }
|
25
69
|
|
26
|
-
|
27
|
-
|
28
|
-
it { expect{request.run}.not_to fail }
|
70
|
+
it { expect{request.run}.to fail }
|
71
|
+
end
|
29
72
|
end
|
30
73
|
end
|
31
74
|
end
|