Changelog¶
Unreleased¶
- Python 3.9 is no longer supported; the floor is 3.10. CPython 3.9
reached end of life in October 2025, and on 2026-10-29 GitHub's
ubuntu-latestrunner moved to Ubuntu 26.04, which no longer ships it, so the floor could no longer be tested.requires-pythonis now>=3.10.
0.4.0¶
discricionariasis new: discretionary and legal transfers, the module that absorbed SICONV, published on the API host in October 2026 as the first of the four stages the government has announced. 12 tables and about 11.8 million rows — more than the other four modules together — covering proposals (1.16 million, plus 45,607 cancelled), proponents, programs, the physical schedule, the detailed application of funds (5.05 million rows, the largest table in these APIs), and the Novo PAC selection process.discricionarias()joins the other module shortcuts, and"siconv"and"discricionarias_e_legais"resolve as aliases. 86 tables, 1,300 columns and 1,308 parameters in all, against 74, 1,045 and 1,059, matching the R sibling field for field.- The new module's endpoints enforce their page limit (200) and their
list-value limit (200) like the others, ignore unknown parameters like the
others, and their row order was verified to be stable across page sizes,
across repeated calls and at depth, which is what makes pagination safe.
Three upstream defects to know about: on
propostasandpropostas-canceladasa malformed list filter answers HTTP 500 rather than the 400 the other endpoints use (client-side validation means a well-formed query never hits this);apoiadores-emendas-programasanswers 500 to itsvalor_repasse_proposta_apoiadores_emendasfilter for every value probed; andpropostas_canceladasholds at least one row the service cannot serialize — as of 2026-10-29, any page containing its 71st row is a 500, so fetching that table from the start fails at the default page size. A smallerpage_size, anoffsetpast the row, or a filter that excludes it are the workarounds until it is fixed upstream. ted()appears on the reference page; it had been missing since 0.3.0.- The joining guide covers
ted, the five tables published in September 2026, and joining through list filters. Every link it draws was checked against the data, including the ones the government's data models do not document yet.
0.3.0¶
The services moved under 0.2.0. Checked by probing on 2026-09-28, and frozen from the same documents as the R sibling, transferegovr 0.2.0, whose schema this one matches table for table, column for column and in which parameters take lists.
Fixed¶
- Every
especiaistable answered 404. The service respelled all 20 paths from_to-(/planos_acao_especiaisis now/planos-acao-especiais). Table names exposed by the package do not change; only the frozen path does.
What is covered¶
tedis back: decentralized credit between federal bodies, now published on the public host, 14 tables. Its 4 nested columns make up the 18 tables of the government's data model.ted()returns alongside the other module shortcuts. Table names differ from 0.1.x's, mostly by becoming plurals:eventois noweventos,trfisprogramacoes_financeiras_trf.especiaisgains 3 tables (returned funds, management report analyses and settlement documents) andparcerias2 (oppandindicacao_beneficiario_emenda_parlamentar), plus new columns and parameters. Three columns ofpropostachanged from integers into enumerations.
74 tables, 1,045 columns and 1,059 parameters, against 55, 811 and 817.
Pagination¶
- The page limit is per module: 200 rows for
especiaisandparcerias, 1000 forfundoafundoandted, read from each module's OpenAPI document.modules()reports it asmax_page_size, andpage_sizenow defaults to it, so largefundoafundoandtedtables need a fifth of the requests. MAX_PAGEstays at 200, the page size every module accepts.
Filters¶
- Some identifier parameters take several values, sent as one
comma-separated value and matching any of them: 113 of them, in all four
modules.
params()marks them asmultipleand gives the most each accepts inmax_values— 100 inespeciais, 200 elsewhere. The OpenAPI documents do not say which parameters these are, and no naming rule does, so the schema builder asks the service. - A list is checked before it is sent: whole numbers only, no missing values, duplicates dropped, and no more than the parameter accepts.
- Any other filter with several values is still refused. A one-element list is now read as its single value.
Testing¶
scripts/mutants.pycarries 34 deliberate defects across pagination, filter validation, list encoding, parsing, transport and schema lookup, and the suite kills all 34. The harness confirms it imports the mutated copy rather than the editable install, and counts a hang as a kill.- The live suite fails on a type warning as well as an error, and checks each module's page limit and every list parameter against the service.
0.2.0¶
The package now targets the public API host,
api-publica.transferegov.gestao.gov.br. That host serves a different kind of
service from the PostgREST one the package was built against, so this release
rewrites the client rather than extending it. Code written against 0.1.x will
need changing.
What is covered¶
parceriasis new: partnership management, 15 tables, including the partnerships and their proposals, budget commitments, payment orders and bank statements. It has no equivalent in the previous release.especiaisreplacestransferenciasespeciais, and grows from 14 tables to 20. The additions are the financial ones: transaction entries, sub-entries, account balances, beneficiaries, and three history tables.fundoafundostays, at 20 tables against the previous 21, with several child tables folded into their parents as columns of lists."transferenciasespeciais"still resolves, as an alias for"especiais".tedis gone. Decentralized credit has not been published on the public API host; it exists only on the older service, which the government is decommissioning on 2026-08-31.ted()andtransferencias_especiais()are removed, andparcerias()andespeciais()take their place alongsidefundo_a_fundo().
55 tables and 811 columns in all, against 48 and 599 — the same totals the R sibling freezes from the same documents.
Filters¶
- Filters are now the endpoints' own typed query parameters rather than
PostgREST operators.
params()lists what each table accepts, with the permitted values of the enumerated ones. - The comparison operators are removed —
eq(),neq(),gt(),gte(),lt(),lte(),like(),ilike(),re_match(),re_imatch(),in_(),is_null(),is_true(),is_false(),not_(),operators()and theFilterclass. These services compare for equality and nothing else. - An unknown parameter name is an error. These services ignore a parameter
they do not recognise and answer
200with the whole table, so a typo would return a plausible, unfiltered result. Names are checked against the frozen schema before the request is made, and a near miss is suggested. - Enumerated values are checked client-side too, so a bad value fails before the round trip rather than as a 422 after it.
- A filter given several values is refused. The service keeps the last occurrence of a repeated parameter and discards the rest without reporting it, so there is no way to express it.
Pagination¶
- Pagination is by page number, and the cap is 200 rows per request rather than
limitandoffsetstill count rows, including when the offset falls inside a page.orderandselectare removed. These APIs publish no ordering or column-selection parameter.- Row order is therefore the server's. It was verified rather than assumed: the same rows come back in the same sequence across page sizes, across repeated calls, 100,000 rows deep, on tables with no key, and on tables with nested columns.
Other changes¶
updated_at()reports when a module's data was last loaded, from the/data-atualizacaoendpoint each module publishes. It is the only freshness signal these APIs give.fields()gains anestedargument, describing the columns of the objects inside a column of lists. 18 columns acrossfundoafundoandparceriasarrive that way because the API folds a child table into its parent.fields()reportsapi_typerather thanpg_type, and no longer reports a primary key: these documents declare none.tables()gainspath, the endpoint a table maps to, andparams, how many filters it accepts. A table may be named with either a hyphen or an underscore.get()andcount()takeuse_cache=where they tookcache=.- A number the API declares but cannot deliver is now reported rather than
silently emptied.
codigo_conta_beneficiario_subtransacao_gestao_financeirais declared an integer and arrives as"***", a masked account number; the column is kept as text with aColumnTypeWarninginstead of coming back all null. - HTTP errors surface the validation detail the service reports, naming the parameter it objected to.
0.1.1¶
- The PyPI project page showed the full text of the MIT license where the
license name belongs, because the metadata pointed at the file rather than
naming the license.
pyproject.tomlnow declares the SPDX expression (PEP 639), so the page reads "MIT". - Recorded the Zenodo DOI: 10.5281/zenodo.21729827 for the project, with a DOI of its own for each version.
0.1.0¶
First release.
- Covers the three TransfereGov open data APIs — special transfers
(
transferenciasespeciais), fund-to-fund transfers (fundoafundo) and decentralized credit (ted) — and the forty-eight tables they publish. get()andcount()query any table;ted(),fundo_a_fundo()andtransferencias_especiais()fix the module.- Filters are keyword arguments named after the columns they apply to. A bare
value means "equals", a bare list means "is one of", and
operators()lists the fifteen comparison operators the services accept. A column whose name collides with one of the keyword arguments goes throughfilters=. - Values carrying commas, parentheses or surrounding spaces are quoted, and numbers are never sent in scientific notation, so a filter matches what it reads as.
modules(),tables()andfields()describe the APIs offline, from a copy of their OpenAPI documents frozen into the package and rebuilt byscripts/build_schema.py.tables(counts=True)adds current row counts.- Columns are typed from that schema rather than inferred, so a column that is
entirely null on one page keeps its dtype on the next, and an empty result
still carries the table's full set of columns. Identifiers declared as
bigintcome back as pandas' nullableInt64, which holds the full 64-bit range. - Pagination collects as many rows as
limitasks for, in pages of at most 1000 — the service's own cap, which it applies silently. Every request carries an explicit order, so pages cannot overlap or skip rows, and the number collected is checked against the total the API reports. metadata()records the total the API reported, the rows and pages retrieved, the order and selection used, and whether the result was cached.- Requests are throttled to sixty a minute and retried with exponential backoff on 429 and 5xx responses. A 400 is not retried, and its PostgREST error body is surfaced with the offending column named.
- A request whose URL grows past what the service accepts fails with a message
naming the cause — a filter built with
in_()over a long sequence. - Responses are cached for an hour, by default in the session's temporary
directory.
cache_dir()switches to a persistent location andcache_clear()empties it.