x 0.12.1 → 0.13.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: 553e0e0e9a87126211bf62942b02cb2c7c8b6d9380ee8fd4336058038c9735b0
4
- data.tar.gz: 34922d1e52a0c04de3c6fe090e854174d78d437c8e14b061a95ca4ab5e1a0438
3
+ metadata.gz: 4f4970075e9256b31a7f53ca6a3a06df7559631b9104c2069372b3797845cd77
4
+ data.tar.gz: a0e25d20e521f8a3408b9e04b8826d7f8959257ecf6748687a0d1b0292719f38
5
5
  SHA512:
6
- metadata.gz: e28c53b17bb053c26326d46940eb539c4612d6f4a547b074e974c2b6ddee616bb37af6c2dd2d4d84b05a21aa2946306de074889b90bd411b2a268793e40d72a0
7
- data.tar.gz: 79ab655009e07915cf71158bb2976301d1519093c753b6f8fc1d128781b4844e867551c3d430bb0226aa113d3c7342efdd401e08d4f3d920a7437dfa8c432ba6
6
+ metadata.gz: 7be87f0b82af146429afa88288f60d53d07185d2ed47f57d534c729758a3bfc426c4a57e11703665eedd19b9d994ce4e51bd4f08e3de84cdc49ca7a2de28eae6
7
+ data.tar.gz: 3e28cfc6ee5d244eff7671e0f724c0a07071d1872792d9fb742ee44bcf2fc2f1a797053dc0d2a415a51a625d85629115f71ad003bb3b9fcaf75049cde6b5d0d1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## [0.13.0] - 2023-12-04
2
+ * Introduce X::RateLimit, which is returned with X::TooManyRequests errors (196caec)
3
+
1
4
  ## [0.12.1] - 2023-11-28
2
5
  * Ensure split chunks are written as binary (c6e257f)
3
6
  * Require tmpdir in X::MediaUploader (9e7c7f1)
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
- ![Tests](https://github.com/sferik/x-ruby/actions/workflows/test.yml/badge.svg)
2
- ![Linter](https://github.com/sferik/x-ruby/actions/workflows/lint.yml/badge.svg)
3
- ![Mutant](https://github.com/sferik/x-ruby/actions/workflows/mutant.yml/badge.svg)
4
- ![Typer Checker](https://github.com/sferik/x-ruby/actions/workflows/type_check.yml/badge.svg)
1
+ [![Tests](https://github.com/sferik/x-ruby/actions/workflows/test.yml/badge.svg)](https://github.com/sferik/x-ruby/actions/workflows/test.yml)
2
+ [![RuboCop](https://github.com/sferik/x-ruby/actions/workflows/rubocop.yml/badge.svg)](https://github.com/sferik/x-ruby/actions/workflows/rubocop.yml)
3
+ [![Mutant](https://github.com/sferik/x-ruby/actions/workflows/mutant.yml/badge.svg)](https://github.com/sferik/x-ruby/actions/workflows/mutant.yml)
4
+ [![Typer Checker](https://github.com/sferik/x-ruby/actions/workflows/steep.yml/badge.svg)](https://github.com/sferik/x-ruby/actions/workflows/steep.yml)
5
5
  [![Gem Version](https://badge.fury.io/rb/x.svg)](https://rubygems.org/gems/x)
6
6
 
7
7
  # A [Ruby](https://www.ruby-lang.org) interface to the [X API](https://developer.x.com)
@@ -55,7 +55,7 @@ v1_client = X::Client.new(base_url: "https://api.twitter.com/1.1/", **x_credenti
55
55
  # Get your account settings
56
56
  v1_client.get("account/settings.json")
57
57
 
58
- # Initialize an X Ads API client
58
+ # Initialize an Ads API client
59
59
  ads_client = X::Client.new(base_url: "https://ads-api.twitter.com/12/", **x_credentials)
60
60
 
61
61
  # Get your ad accounts
@@ -80,6 +80,23 @@ The tests for the previous version of this library executed in about 2 seconds.
80
80
 
81
81
  This code is not littered with comments that are intended to generate documentation. Rather, this code is intended to be simple enough to serve as its own documentation. If you want to understand how something works, don’t read the documentation—it might be wrong—read the code. The code is always right.
82
82
 
83
+ ## Features
84
+
85
+ If this entire library is implemented in just 500 lines of code, why should you use it at all vs. writing your own library that suits your needs? If you feel inspired to do that, don’t let me discourage you, but this library has some advanced features that may not be apparent without diving into the code:
86
+
87
+ * OAuth 1.0 Revision A
88
+ * OAuth 2.0 Bearer Token
89
+ * Thread safety
90
+ * HTTP redirect following
91
+ * HTTP proxy support
92
+ * HTTP logging
93
+ * HTTP timeout configuration
94
+ * HTTP error handling
95
+ * Rate limit handling
96
+ * Parsing JSON into custom response objects (e.g. OpenStruct)
97
+ * Configurable base URLs for accessing different APIs/versions
98
+ * Parallel uploading of large media files in chunks
99
+
83
100
  ## Sponsorship
84
101
 
85
102
  The X gem is free to use, but with X API pricing tiers, it actually costs money to develop and maintain. By contributing to the project, you help us:
@@ -132,9 +149,9 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/sferik
132
149
 
133
150
  Pull requests will only be accepted if they meet all the following criteria:
134
151
 
135
- 1. Code must conform to [Standard Ruby](https://github.com/standardrb/standard). This can be verified with:
152
+ 1. Code must conform to [RuboCop rules](https://github.com/rubocop/rubocop). This can be verified with:
136
153
 
137
- bundle exec rake standard
154
+ bundle exec rubocop
138
155
 
139
156
  2. 100% C0 code coverage. This can be verified with:
140
157
 
@@ -142,6 +159,7 @@ Pull requests will only be accepted if they meet all the following criteria:
142
159
 
143
160
  3. 100% mutation coverage. This can be verified with:
144
161
 
162
+ git remote add upstream https://github.com/sferik/x-ruby
145
163
  bundle exec rake mutant
146
164
 
147
165
  4. RBS type signatures (in `sig/x.rbs`). This can be verified with:
@@ -1,17 +1,20 @@
1
1
  require_relative "client_error"
2
+ require_relative "../rate_limit"
2
3
 
3
4
  module X
4
5
  class TooManyRequests < ClientError
5
- def limit
6
- response["x-rate-limit-limit"].to_i
6
+ def rate_limit
7
+ rate_limits.max_by(&:reset_at)
7
8
  end
8
9
 
9
- def remaining
10
- response["x-rate-limit-remaining"].to_i
10
+ def rate_limits
11
+ @rate_limits ||= RateLimit::TYPES.filter_map do |type|
12
+ RateLimit.new(type: type, response: response) if response["x-#{type}-remaining"].eql?("0")
13
+ end
11
14
  end
12
15
 
13
16
  def reset_at
14
- Time.at(response["x-rate-limit-reset"].to_i)
17
+ rate_limit&.reset_at || Time.at(0)
15
18
  end
16
19
 
17
20
  def reset_in
@@ -0,0 +1,33 @@
1
+ module X
2
+ class RateLimit
3
+ RATE_LIMIT_TYPE = "rate-limit".freeze
4
+ APP_LIMIT_TYPE = "app-limit-24hour".freeze
5
+ USER_LIMIT_TYPE = "user-limit-24hour".freeze
6
+ TYPES = [RATE_LIMIT_TYPE, APP_LIMIT_TYPE, USER_LIMIT_TYPE].freeze
7
+
8
+ attr_accessor :type, :response
9
+
10
+ def initialize(type:, response:)
11
+ @type = type
12
+ @response = response
13
+ end
14
+
15
+ def limit
16
+ Integer(response.fetch("x-#{type}-limit"))
17
+ end
18
+
19
+ def remaining
20
+ Integer(response.fetch("x-#{type}-remaining"))
21
+ end
22
+
23
+ def reset_at
24
+ Time.at(Integer(response.fetch("x-#{type}-reset")))
25
+ end
26
+
27
+ def reset_in
28
+ [(reset_at - Time.now).ceil, 0].max
29
+ end
30
+
31
+ alias_method :retry_after, :reset_in
32
+ end
33
+ end
data/lib/x/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require "rubygems/version"
2
2
 
3
3
  module X
4
- VERSION = Gem::Version.create("0.12.1")
4
+ VERSION = Gem::Version.create("0.13.0")
5
5
  end
data/sig/x.rbs CHANGED
@@ -102,10 +102,10 @@ module X
102
102
  end
103
103
 
104
104
  class TooManyRequests < ClientError
105
- @response: Net::HTTPResponse
105
+ @rate_limits: Array[RateLimit]
106
106
 
107
- def limit: -> Integer
108
- def remaining: -> Integer
107
+ def rate_limit: -> RateLimit?
108
+ def rate_limits: -> Array[RateLimit]
109
109
  def reset_at: -> Time
110
110
  def reset_in: -> Integer?
111
111
  end
@@ -147,6 +147,21 @@ module X
147
147
  def configure_http_client: (Net::HTTP http_client) -> Net::HTTP
148
148
  end
149
149
 
150
+ class RateLimit
151
+ RATE_LIMIT_TYPE: String
152
+ APP_LIMIT_TYPE: String
153
+ USER_LIMIT_TYPE: String
154
+ TYPES: [String, String, String]
155
+
156
+ attr_accessor type: String
157
+ attr_accessor response: Net::HTTPResponse
158
+ def initialize: (type: String, response: Net::HTTPResponse) -> void
159
+ def limit: -> Integer
160
+ def remaining: -> Integer
161
+ def reset_at: -> Time
162
+ def reset_in: -> Integer?
163
+ end
164
+
150
165
  class RequestBuilder
151
166
  HTTP_METHODS: Hash[Symbol, (singleton(Net::HTTP::Get) | singleton(Net::HTTP::Post) | singleton(Net::HTTP::Put) | singleton(Net::HTTP::Delete))]
152
167
  DEFAULT_HEADERS: Hash[String, String]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: x
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Berlin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-28 00:00:00.000000000 Z
11
+ date: 2023-12-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -50,6 +50,7 @@ files:
50
50
  - lib/x/errors/unprocessable_entity.rb
51
51
  - lib/x/media_uploader.rb
52
52
  - lib/x/oauth_authenticator.rb
53
+ - lib/x/rate_limit.rb
53
54
  - lib/x/redirect_handler.rb
54
55
  - lib/x/request_builder.rb
55
56
  - lib/x/response_parser.rb