The cap you cannot see
The service returns at most 1000 rows per request,
however many are asked for, and it does so without complaint: ask for a
hundred thousand and you are given a thousand and a
206 Partial Content. Nothing in the body says the result
was truncated. Only the Content-Range header does.
This package reads that header, which is why .limit
counts rows rather than requests, and why anything above 1000 is
collected page by page:
plans <- tg_get("ted", "plano_acao", .limit = 2500)
nrow(plans)
#> [1] 2500
tg_metadata(plans)$pages
#> [1] 3.page_size is capped at 1000 for the same reason. A
larger value would be accepted by the package, silently truncated by the
service, and the shortfall would look like missing data.
Ask how big it is first
tg_count("fundoafundo", "gestao_financeira_lancamentos")
#> [1] 1115444To size everything at once, tg_tables() takes a
counts argument. It is the only part of that function that
touches the network — one request per table, cached, so the second call
in a session is free:
tg_tables(counts = TRUE) |>
dplyr::arrange(desc(rows))
#> # A tibble: 48 × 6
#> module table columns rows …
#> <chr> <chr> <int> <dbl>
#> 1 fundoafundo gestao_financeira_lancamentos 32 1115444
#> 2 fundoafundo gestao_financeira_subtransacoes 16 377666
#> 3 transferenciasespeciais historico_pagamento_especial 5 281163
#> 4 fundoafundo plano_acao_historico 5 183379
#> 5 transferenciasespeciais meta_especial 16 156016
#> # ℹ 43 more rowsA million rows is over a thousand requests. The package throttles itself to sixty requests a minute by default, so that download takes something like twenty minutes. That is usually the wrong shape for the question being asked.
Narrow first, then collect
Two things make a large query smaller, and both happen on the server:
Select only the columns you need.
gestao_financeira_lancamentos has 32 columns; if you want
four of them, the other 28 need never cross the network.
tg_get(
"fundoafundo", "gestao_financeira_lancamentos",
.select = c(
"id_lancamento_gestao_financeira",
"id_plano_acao",
"data_lancamento_gestao_financeira",
"valor_lancamento_gestao_financeira"
),
.limit = Inf
)Filter before you fetch. A year’s worth of a table is usually what was wanted anyway:
Why the order matters
Offset pagination asks for rows 0–999, then 1000–1999, and so on. In
Postgres a query without ORDER BY has no defined
row order: the planner is free to return rows differently
between two executions, and if it does, page two can repeat rows from
page one and skip others entirely. The result has the right number of
rows and the wrong contents.
So every request this package makes carries an explicit order. By default it is the table’s primary key where the API declares one, and its identifier columns otherwise:
tg_metadata(plans)$order
#> [1] "id_plano_acao.asc" "id_programa.asc" "sq_instrumento.asc"You can supply your own, and pagination will use it:
tg_get("ted", "plano_acao", .order = "vl_total_plano_acao.desc", .limit = 2500)Bear in mind that ordering by a column with many ties leaves the order within each tie undefined, which brings the problem back. Prefer an identifier, or add one as a tiebreaker:
Resuming, and taking it in pieces
.offset skips rows that have already been collected, so
a long download can be taken in sittings:
first <- tg_get("ted", "plano_acao_etapa", .limit = 20000)
rest <- tg_get("ted", "plano_acao_etapa", .limit = Inf, .offset = 20000)Both parts must use the same order, or they are pages of two
different sequences. The default order is deterministic for a given
table, so leaving .order alone is the safe choice here.
When the count and the contents disagree
The number of rows collected is checked against the total the API reported:
Warning: Collected 4820 rows where the API reported 4900.
i The table may have changed while it was being read.
i Check `tg_metadata()` on the result.
This is a real possibility on a long download, since the underlying
tables are refreshed daily. It is a warning rather than an error because
a nearly complete result is still worth having — but it must be visible,
and tg_metadata() records both numbers so the gap stays in
the data rather than only in the console.
Caching
Every request is cached for an hour, so re-running an analysis does not re-fetch what it already has. Each page is cached separately, so an interrupted collection resumes from the network only where it has to.
tg_cache_dir()
#> [1] "/tmp/RtmpXXXX/transferegovr-cache"The default is the session’s temporary directory, so nothing outlives the session unless you ask for it:
tg_cache_dir(tools::R_user_dir("transferegovr", "cache"))Set TRANSFEREGOVR_CACHE_DIR in .Renviron to
make that permanent, and adjust the lifetime with
options(transferegovr.cache_ttl = 86400) — the data behind
these APIs is refreshed once a day, so an hour is conservative.
tg_metadata(x)$cached tells you whether a result came
from the network or from disk.
Rate limiting and retries
Requests are throttled to sixty a minute and retried up to four times on a 429 or a 5xx, with exponential backoff and jitter. A 400 is not retried: it means the service rejected the query itself and will reject it identically next time.
Both are adjustable, but the defaults exist to keep the package a considerate client of a public service:
options(
transferegovr.requests_per_minute = 30,
transferegovr.max_tries = 6,
transferegovr.timeout = 120
)