brapi-ruby-sdk 0.3.0 → 0.4.0
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/CHANGELOG.md +25 -0
- data/README.md +41 -0
- data/lib/brapi/resources/paginated.rb +114 -0
- data/lib/brapi/resources/quote.rb +9 -0
- data/lib/brapi/resources/v2/fii.rb +4 -0
- data/lib/brapi/resources/v2/treasury.rb +4 -0
- data/lib/brapi/version.rb +1 -1
- data/lib/brapi.rb +3 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2f184be4deccafcbafd23f9275a7801290357e72c13d78eee0d5604aaa60edd2
|
|
4
|
+
data.tar.gz: f3e70b267473bc7dc2222463a7f175578b8ad3099657dccddf71571237e884e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 116a1de195f79a3b4943ae68ad6f0b9133f66145282e0f124564f2083a5a7b87d5325e65502fcaf059f49b2538bda73a3ef304c0ef9b8ffb8b3746bc41f4c8ad
|
|
7
|
+
data.tar.gz: f6516906c904e17d2732d108f4b989c30bba0a9477144aeeb59db534f640391b1d0937df282e600c6837ff39899bb52eb0f51eecebbcea7ea8b19b4993684b1b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-05-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Auto-pagination** on every paginated resource. Three new public methods
|
|
15
|
+
show up on `client.quote`, `client.v2.fii` and `client.v2.treasury`:
|
|
16
|
+
- `#each_page { |page| ... }` — yields one full page response per iteration
|
|
17
|
+
until the upstream signals `has_next_page = false` (or after `max_pages:`
|
|
18
|
+
iterations, whichever comes first).
|
|
19
|
+
- `#each { |item| ... }` — auto-flattens across pages, yielding each row
|
|
20
|
+
(FII, bond or QuoteListItem) individually.
|
|
21
|
+
- Without a block, both return an `Enumerator` so the full Ruby
|
|
22
|
+
Enumerable surface is available: `client.v2.fii.first(10)`,
|
|
23
|
+
`client.v2.fii.select { |f| f.dividend_yield12m > 0.1 }`,
|
|
24
|
+
`client.v2.treasury.lazy.find { |b| b.indexer == "ipca" }`, etc.
|
|
25
|
+
- `Brapi::Resources::Paginated` mixin (declarative `paginates items: :fiis`
|
|
26
|
+
in the resource class) ready to be reused by any future paginated
|
|
27
|
+
endpoint.
|
|
28
|
+
- `max_pages:` keyword on `#each` / `#each_page` (default `10_000`)
|
|
29
|
+
protects against runaway loops if a buggy upstream forgets to flip
|
|
30
|
+
`has_next_page = false`.
|
|
31
|
+
- Resources are now `Enumerable`, so the standard `map` / `select` /
|
|
32
|
+
`take` / `find` / `lazy` / `count` methods Just Work on paginated
|
|
33
|
+
endpoints.
|
|
34
|
+
|
|
10
35
|
## [0.3.0] - 2026-05-28
|
|
11
36
|
|
|
12
37
|
### Added
|
data/README.md
CHANGED
|
@@ -220,6 +220,47 @@ resp.stocks.each { |s| puts "#{s.stock} (#{s.name}): R$ #{s.close}" }
|
|
|
220
220
|
puts "Page #{resp.current_page}/#{resp.total_pages}"
|
|
221
221
|
```
|
|
222
222
|
|
|
223
|
+
### Auto-pagination
|
|
224
|
+
|
|
225
|
+
Every paginated resource (`client.quote.list`, `client.v2.fii.list`,
|
|
226
|
+
`client.v2.treasury.list`) gains `#each_page`, `#each` and the full
|
|
227
|
+
Ruby `Enumerable` surface:
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
# Yield each row across all pages — lazy by default, stops when
|
|
231
|
+
# the API reports has_next_page = false.
|
|
232
|
+
Brapi.v2.fii.each { |f| puts "#{f.symbol}: DY12m=#{f.dividend_yield12m}" }
|
|
233
|
+
|
|
234
|
+
# Or yield one page at a time:
|
|
235
|
+
Brapi.v2.treasury.each_page(max_pages: 5) do |page|
|
|
236
|
+
puts "Page #{page.pagination.page}/#{page.pagination.total_pages}"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Enumerable methods Just Work — `each` without a block returns
|
|
240
|
+
# an Enumerator, so you can use first/select/lazy/etc.:
|
|
241
|
+
top_yield = Brapi.v2.fii
|
|
242
|
+
.first(100)
|
|
243
|
+
.sort_by { |f| -(f.dividend_yield12m || 0) }
|
|
244
|
+
.first(5)
|
|
245
|
+
|
|
246
|
+
ipca_bonds = Brapi.v2.treasury
|
|
247
|
+
.lazy
|
|
248
|
+
.select { |b| b.indexer == "ipca" }
|
|
249
|
+
.first(10)
|
|
250
|
+
|
|
251
|
+
# Custom params pass through to the underlying `list` call:
|
|
252
|
+
Brapi.quote.each(sort_by: "volume", sort_order: "desc") { |s| ... }
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`max_pages:` (default `10_000`) caps the walk in case the upstream
|
|
256
|
+
forgets to signal the end. `page:` lets you start from a specific page.
|
|
257
|
+
|
|
258
|
+
`count` and `size` take a fast path: they fetch only the first page and
|
|
259
|
+
read `pagination.total_items` (or `item_count` on Quote), so
|
|
260
|
+
`Brapi.v2.fii.count` is one HTTP call, not 81. Pass a block to `count`
|
|
261
|
+
when you want Enumerable filtering semantics — that still walks every
|
|
262
|
+
page, as expected.
|
|
263
|
+
|
|
223
264
|
## Error handling
|
|
224
265
|
|
|
225
266
|
All errors inherit from `Brapi::Error`:
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Brapi
|
|
4
|
+
module Resources
|
|
5
|
+
# Adds auto-pagination helpers (`#each_page`, `#each`, plus a fast
|
|
6
|
+
# `#count` / `#size`) to a Resource around an existing list-style method.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
#
|
|
10
|
+
# class Brapi::Resources::V2::Fii < Brapi::Resource
|
|
11
|
+
# include Brapi::Resources::Paginated
|
|
12
|
+
# paginates items: :fiis
|
|
13
|
+
#
|
|
14
|
+
# def list(**params)
|
|
15
|
+
# # ...
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# ## Requirements on the host
|
|
20
|
+
#
|
|
21
|
+
# The list-style method named by `via:` (default `:list`) must accept a
|
|
22
|
+
# `page:` keyword argument. The mixin sets this on every iteration to walk
|
|
23
|
+
# through pages; if the host's `list` rejects `page:` the first iteration
|
|
24
|
+
# will raise `ArgumentError`.
|
|
25
|
+
#
|
|
26
|
+
# ## Pagination shapes
|
|
27
|
+
#
|
|
28
|
+
# Two shapes are supported:
|
|
29
|
+
#
|
|
30
|
+
# 1. **Nested** (FII / Treasury): the response has a `pagination` sub-object
|
|
31
|
+
# with `page`, `has_next_page`, `total_items`. This is the default.
|
|
32
|
+
# 2. **Flat** (Quote#list): the response exposes `current_page` /
|
|
33
|
+
# `has_next_page` / `item_count` directly. Pass `has_next:`, `next_page:`
|
|
34
|
+
# and `count_from:` lambdas to override the readers.
|
|
35
|
+
#
|
|
36
|
+
# ## Safety
|
|
37
|
+
#
|
|
38
|
+
# `max_pages:` (default `DEFAULT_MAX_PAGES`) caps any walk — protects
|
|
39
|
+
# against runaway loops if the upstream forgets to set
|
|
40
|
+
# `has_next_page = false`. The cap is checked **before** each fetch so
|
|
41
|
+
# `max_pages: 0` yields nothing.
|
|
42
|
+
#
|
|
43
|
+
# ## #count behaviour
|
|
44
|
+
#
|
|
45
|
+
# When called with no args and no block, `#count` fetches **only the first
|
|
46
|
+
# page** and reads `pagination.total_items` (or whatever `count_from:`
|
|
47
|
+
# returns), avoiding a full walk. When called with an item or a block, it
|
|
48
|
+
# delegates to the standard `Enumerable#count` (which walks every page).
|
|
49
|
+
module Paginated
|
|
50
|
+
DEFAULT_MAX_PAGES = 10_000
|
|
51
|
+
|
|
52
|
+
def self.included(base)
|
|
53
|
+
base.include(Enumerable)
|
|
54
|
+
base.extend(ClassMethods)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module ClassMethods
|
|
58
|
+
def paginates(items:, via: :list, has_next: nil, next_page: nil, count_from: nil)
|
|
59
|
+
has_next ||= ->(resp) { resp.pagination&.has_next_page }
|
|
60
|
+
next_page ||= ->(resp) { (resp.pagination&.page || 0) + 1 }
|
|
61
|
+
count_from ||= ->(resp) { resp.pagination&.total_items }
|
|
62
|
+
|
|
63
|
+
define_each_page(via: via, has_next: has_next, next_page: next_page)
|
|
64
|
+
define_each(items: items)
|
|
65
|
+
define_count(via: via, count_from: count_from)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def define_each_page(via:, has_next:, next_page:)
|
|
71
|
+
define_method(:each_page) do |max_pages: Brapi::Resources::Paginated::DEFAULT_MAX_PAGES, **params, &block|
|
|
72
|
+
return enum_for(:each_page, max_pages: max_pages, **params) unless block
|
|
73
|
+
|
|
74
|
+
current = params.delete(:page) || 1
|
|
75
|
+
pages_seen = 0
|
|
76
|
+
loop do
|
|
77
|
+
break if pages_seen >= max_pages
|
|
78
|
+
|
|
79
|
+
resp = public_send(via, **params, page: current)
|
|
80
|
+
block.call(resp)
|
|
81
|
+
pages_seen += 1
|
|
82
|
+
break unless has_next.call(resp)
|
|
83
|
+
|
|
84
|
+
current = next_page.call(resp)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def define_each(items:)
|
|
90
|
+
define_method(:each) do |max_pages: Brapi::Resources::Paginated::DEFAULT_MAX_PAGES, **params, &block|
|
|
91
|
+
return enum_for(:each, max_pages: max_pages, **params) unless block
|
|
92
|
+
|
|
93
|
+
each_page(max_pages: max_pages, **params) do |resp|
|
|
94
|
+
resp.public_send(items).each { |item| block.call(item) }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def define_count(via:, count_from:)
|
|
100
|
+
define_method(:count) do |*args, &block|
|
|
101
|
+
# count(item) and count { block } use Enumerable's filtering
|
|
102
|
+
# semantics — fall back to the standard walk.
|
|
103
|
+
return super(*args, &block) unless args.empty? && block.nil?
|
|
104
|
+
|
|
105
|
+
total = count_from.call(public_send(via, page: 1))
|
|
106
|
+
total.nil? ? super(*args, &block) : total
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
define_method(:size) { count }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -5,6 +5,15 @@ require "cgi"
|
|
|
5
5
|
module Brapi
|
|
6
6
|
module Resources
|
|
7
7
|
class Quote < Brapi::Resource
|
|
8
|
+
include Brapi::Resources::Paginated
|
|
9
|
+
|
|
10
|
+
# rubocop:disable Style/SymbolProc -- arrow-form keeps the three lambdas visually aligned
|
|
11
|
+
paginates items: :stocks,
|
|
12
|
+
has_next: ->(r) { r.has_next_page },
|
|
13
|
+
next_page: ->(r) { (r.current_page || 0) + 1 },
|
|
14
|
+
count_from: ->(r) { r.item_count }
|
|
15
|
+
# rubocop:enable Style/SymbolProc
|
|
16
|
+
|
|
8
17
|
# GET /api/quote/{tickers}
|
|
9
18
|
def retrieve(tickers, **params)
|
|
10
19
|
tickers_str = Array(tickers).join(",")
|
data/lib/brapi/version.rb
CHANGED
data/lib/brapi.rb
CHANGED
|
@@ -63,7 +63,9 @@ require "brapi/models/v2/treasury_bond"
|
|
|
63
63
|
require "brapi/models/v2/treasury_list_response"
|
|
64
64
|
require "brapi/models/v2/treasury_indicators_response"
|
|
65
65
|
|
|
66
|
-
# Resources — v2 (parent class) must load before its nested classes
|
|
66
|
+
# Resources — v2 (parent class) must load before its nested classes,
|
|
67
|
+
# and Paginated must load before any resource that includes it.
|
|
68
|
+
require "brapi/resources/paginated"
|
|
67
69
|
require "brapi/resources/quote"
|
|
68
70
|
require "brapi/resources/available"
|
|
69
71
|
require "brapi/resources/v2"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brapi-ruby-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rômulo Storel
|
|
@@ -102,6 +102,7 @@ files:
|
|
|
102
102
|
- lib/brapi/models/value_added_entry.rb
|
|
103
103
|
- lib/brapi/resource.rb
|
|
104
104
|
- lib/brapi/resources/available.rb
|
|
105
|
+
- lib/brapi/resources/paginated.rb
|
|
105
106
|
- lib/brapi/resources/quote.rb
|
|
106
107
|
- lib/brapi/resources/v2.rb
|
|
107
108
|
- lib/brapi/resources/v2/crypto.rb
|