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.01010';
3 3     3   2393 use Moose::Role;
  3         9  
  3         26  
4 3     3   14462 use namespace::autoclean;
  3         6  
  3         29  
5 3     3   233 use Try::Tiny;
  3         4  
  3         217  
6 3     3   17 use Scalar::Util qw/weaken/;
  3         4  
  3         2049  
7              
8             =head1 NAME
9              
10             Net::Twitter::Role::RateLimit - Rate limit features for Net::Twitter
11              
12             =head1 VERSION
13              
14             version 4.01010
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 279 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 112 my $self = shift;
117              
118             # If rate_reset is in the past, we need to refresh it
119 11 100       677 $self->rate_limit_status if $self->_rate_limit_status->{rate_reset} < time;
120              
121             # HACK! Prevent a loop on clock mismatch
122 11         20 my $time = time;
123 11 100       536 if ( $self->_rate_limit_status->{rate_reset} < $time ) {
124 1         51 $self->_rate_limit_status->{rate_reset} = $time + 1;
125             }
126              
127 11         598 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 152 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 699 my $self = shift;
161              
162 1         9 my $full_rate = $self->rate_limit / 3600;
163 1   50 1   12 my $current_rate = try { $self->rate_remaining / ($self->rate_reset - time) } || 0;
  1         36  
164 1         25 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 556 my ( $self, $target_rate ) = @_;
191              
192 1         3 my $s = $self->rate_reset - time - 3600 * $self->rate_remaining / $target_rate / $self->rate_limit;
193 1 50       8 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) 2009 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