File Coverage

blib/lib/Net/Twitter/Role/RateLimit.pm
Criterion Covered Total %
statement 28 28 100.0
branch 5 6 83.3
condition 1 2 50.0
subroutine 10 10 100.0
pod 5 5 100.0
total 49 51 96.0


line stmt bran cond sub pod time code
1             package Net::Twitter::Role::RateLimit;
2             $Net::Twitter::Role::RateLimit::VERSION = '4.01043';
3 3     3   1634 use Moose::Role;
  3         6  
  3         20  
4 3     3   12779 use namespace::autoclean;
  3         6  
  3         24  
5 3     3   215 use Try::Tiny;
  3         5  
  3         176  
6 3     3   16 use Scalar::Util qw/weaken/;
  3         4  
  3         1808  
7              
8             =head1 NAME
9            
10             Net::Twitter::Role::RateLimit - Rate limit features for Net::Twitter
11            
12             =head1 VERSION
13            
14             version 4.01043
15            
16             =head1 SYNOPSIS
17            
18             use Net::Twitter;
19             my $nt = Net::Twitter->new(
20             traits => [qw/API::REST RateLimit/],
21             %other_options,
22             );
23            
24             #...later
25            
26             sleep $nt->until_rate(1.0) || $minimum_wait;
27            
28             =head1 NOTE!
29            
30             RateLimit only works with Twitter API v1. The rate limiting strategy of Twitter
31             API v1.1 is very different. A v1.1 compatible RateLimit role may be coming, but
32             isn't available, yet. It's interface will necessarily be different.
33            
34             =head1 DESCRIPTION
35            
36             This provides utility methods that return information about the current
37             rate limit status.
38            
39             =cut
40              
41             requires qw/ua rate_limit_status/;
42              
43             # Rate limiting changed so dramatically with v1.1 this Role simply won't work with it
44             excludes 'Net::Twitter::Role::API::RESTv1_1';
45              
46             has _rate_limit_status => (
47                 isa => 'HashRef[Int]',
48                 is => 'rw',
49                 init_arg => undef,
50                 lazy => 1,
51                 default => sub { my %h; @h{qw/rate_limit rate_reset rate_remaining/} = (0,0,0); \%h },
52             );
53              
54             around rate_limit_status => sub {
55                 my $orig = shift;
56                 my $self = shift;
57              
58                 my $r = $self->$orig(@_) || return;
59              
60                 @{$self->_rate_limit_status}{qw/rate_remaining rate_reset rate_limit/} =
61                     @{$r}{qw/remaining_hits reset_time_in_seconds hourly_limit/};
62              
63                 return $r;
64             };
65              
66             for my $method ( qw/rate_remaining rate_limit/ ) {
67                around $method => sub {
68                     my $orig = shift;
69                     my $self = shift;
70              
71                     $self->rate_reset; # force a call to rate_limit_satus if necessary;
72              
73                     return $self->$orig(@_);
74                 };
75             }
76              
77             after BUILD => sub {
78                 my $self = shift;
79              
80                 weaken $self;
81              
82                 $self->ua->add_handler(response_done => sub {
83                     my $res = shift;
84              
85                     my @values = map { $res->header($_) }
86                                  qw/x-ratelimit-remaining x-ratelimit-reset x-ratelimit-limit/;
87              
88                     return unless @values == 3;
89              
90                     @{$self->_rate_limit_status}{qw/rate_remaining rate_reset rate_limit/} = @values;
91                 });
92             };
93              
94             =head1 METHODS
95            
96             If current rate limit data is not resident, these methods will force a call to
97             C<rate_limit_status>. Therefore, any of these methods can throw an error.
98            
99             =over 4
100            
101             =item rate_remaining
102            
103             Returns the number of API calls available before the next reset.
104            
105             =cut
106              
107 4     4 1 102 sub rate_remaining { shift->_rate_limit_status->{rate_remaining} }
108              
109             =item rate_reset
110            
111             Returns the Unix epoch time of the next reset.
112            
113             =cut
114              
115             sub rate_reset {
116 11     11 1 15     my $self = shift;
117              
118             # If rate_reset is in the past, we need to refresh it
119 11 100       332     $self->rate_limit_status if $self->_rate_limit_status->{rate_reset} < time;
120              
121             # HACK! Prevent a loop on clock mismatch
122 11         14     my $time = time;
123 11 100       286     if ( $self->_rate_limit_status->{rate_reset} < $time ) {
124 1         28         $self->_rate_limit_status->{rate_reset} = $time + 1;
125                 }
126              
127 11         280     return $self->_rate_limit_status->{rate_reset};
128             }
129              
130             =item rate_limit
131            
132             Returns the current hourly rate limit.
133            
134             =cut
135              
136 3     3 1 78 sub rate_limit { shift->_rate_limit_status->{rate_limit} }
137              
138             =item rate_ratio
139            
140             Returns remaining API call limit, divided by the time remaining before the next
141             reset, as a ratio of the total rate limit per hour.
142            
143             For example, if C<rate_limit> is 150, the total rate is 150 API calls per hour.
144             If C<rate_remaining> is 75, and there 1800 seconds (1/2 hour) remaining before
145             the next reset, C<rate_ratio> returns 1.0, because there are exactly enough
146             API calls remaining to maintain he full rate of 150 calls per hour.
147            
148             If C<rate_remaining> is 30 and there are 360 seconds remaining before reset,
149             C<rate_ratio> returns 2.0, because there are enough API calls remaining
150             to maintain twice the full rate of 150 calls per hour.
151            
152             As a final example, if C<rate_remaining> is 15, and there are 7200 seconds
153             remaining before reset, C<rate_ratio> returns 0.5, because there are only
154             enough API calls remaining to maintain half the full rate of 150 calls per
155             hour.
156            
157             =cut
158              
159             sub rate_ratio {
160 1     1 1 602     my $self = shift;
161              
162 1         4     my $full_rate = $self->rate_limit / 3600;
163 1   50 1   7     my $current_rate = try { $self->rate_remaining / ($self->rate_reset - time) } || 0;
  1         26  
164 1         15     return $current_rate / $full_rate;
165             }
166              
167             =item until_rate($target_ratio)
168            
169             Returns the number of seconds to wait before making another rate limited API
170             call such that C<$target_ratio> of the full rate would be available. It
171             always returns a number greater than, or equal to zero.
172            
173             Use a target rate of 1.0 in a timeline polling loop to get a steady polling
174             rate, using all the allocated calls, and adjusted for other API calls as they
175             occur.
176            
177             Use a target rate E<lt> 1.0 to allow a process to make calls as fast as
178             possible but not consume all of the calls available, too soon. For example, if
179             you have a process building a large social graph, you may want to allow it make
180             as many calls as possible, with no wait, until 20% of the available rate
181             remains. Use a value of 0.2 for that purpose.
182            
183             A target rate E<gt> than 1.0 can be used for a process that should only use
184             "extra" available API calls. This is useful for an application that requires
185             most of it's rate limit for normal operation.
186            
187             =cut
188              
189             sub until_rate {
190 1     1 1 348     my ( $self, $target_rate ) = @_;
191              
192 1         2     my $s = $self->rate_reset - time - 3600 * $self->rate_remaining / $target_rate / $self->rate_limit;
193 1 50       5     return $s > 0 ? $s : 0;
194             };
195              
196             1;
197              
198             __END__
199            
200             =back
201            
202             =head1 AUTHOR
203            
204             Marc Mims <marc@questright.com>
205            
206             =head1 LICENSE
207            
208             Copyright (c) 2016 Marc Mims
209            
210             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
211            
212             =cut
213