Exchange rates API¶
The BNR rate client and its value types.
anafpy.bnr.client ¶
Async client for BNR's published foreign-exchange reference rates.
The one non-ANAF source in anafpy (DESIGN.md §17). Romanian tax filings are
denominated in lei, so a foreign-currency invoice has to be converted before it
can be declared — e-Transport's value_ron, e-Factura's BT-111, D301's
curs_valutar. BNR is who publishes the reference rate that conversion uses,
and this client is a rate source, not a ruling: it returns what BNR
published on a date. Which date a given filing must use is the caller's
question, not this module's answer.
BNR publishes static XML documents on curs.bnr.ro, no auth and no key
(schema at https://curs.bnr.ro/xsd/nbrfxrates.xsd):
nbrfxrates.xml— the current rates (one day);nbrfxrates10days.xml— the last ten banking days;files/xml/years/nbrfxrates<YYYY>.xml— one year, back to 2005, regenerated daily so the current year reaches yesterday's close.
Two things shape the code. Rates exist per banking day, published just after
13:00, so a request for a weekend, a holiday, or this morning has no rate of its
own: :meth:BnrClient.get_rates resolves to the latest day on or before the
one asked for and reports that day in :attr:~anafpy.bnr.models.FxRateSet.date
— it never labels Friday's rate as Sunday's. And BNR asks callers to cache
rather than re-fetch, and to read these files instead of scraping the site
pages; hence the smallest-sufficient document per query (1.8 KB for today,
14 KB for the last fortnight, the 350 KB year archive only for older dates)
and an in-process cache keyed to how mutable each document actually is.
BnrClient ¶
BnrClient(*, http: AsyncClient | None = None, timeout: float = 30.0)
Bases: HttpClientBase
Reads BNR's published exchange-rate XML (curs.bnr.ro).
No credentials, no test/prod split — like
:class:~anafpy.public.client.PublicClient, but a different publisher.
Use it as an async context manager so an owned client closes cleanly; an
injected client must carry a non-empty base_url.
Fetched documents are cached in-process, because BNR asks callers to store
what they take rather than re-fetch it. The lifetime follows what BNR can
still change: a closed year's archive is final and never expires, while
a document BNR is still updating — today's file, the ten-day window, the
current year — is re-read after :data:_LIVE_TTL. Concurrent misses share
one fetch, and a failed fetch is not cached.
resolve_date ¶
resolve_date(date: date | str | None = None) -> date
The day a lookup is for: the caller's, or today in Romania.
The one definition of that default. A caller who has to report which day it asked about — the MCP tool does, so a banking-day fallback is visible — must not compute it a second time: two clocks drift apart, and the one that drifts is the one nothing tests.
Raises:
| Type | Description |
|---|---|
AnafConfigError
|
date is not an ISO date. |
get_rates
async
¶
get_rates(date: date | str | None = None) -> FxRateSet
The reference rates in force for date (default: today in Romania).
Rates are published per banking day just after 13:00, so the result is
the latest set BNR published on or before date — read
:attr:~anafpy.bnr.models.FxRateSet.date to see which day that was
rather than assuming it is the one requested.
Raises:
| Type | Description |
|---|---|
AnafConfigError
|
date is unparseable or predates BNR's archive. |
AnafResponseError
|
BNR served something that is not a rate document, or published nothing on or before date. |
convert
async
¶
convert(amount: Decimal | int | str, currency: str, *, date: date | str | None = None) -> Conversion
Convert one amount to RON at date's published rate.
Convenience over :meth:get_rates plus
:meth:~anafpy.bnr.models.FxRateSet.convert. To convert several
amounts at one rate — an invoice's lines — fetch the set once and call
its convert per amount, so every line demonstrably shares a rate.
anafpy.bnr.models ¶
Value types for BNR's published foreign-exchange reference rates.
BNR quotes lei per unit of foreign currency, with a multiplier attribute
on the small-denomination currencies: <Rate currency="HUF" multiplier="100">
1.4430</Rate> means 100 HUF = 1.4430 RON, not 1 HUF. Keeping quoted and
multiplier exactly as published — and dividing only inside
:meth:FxRateSet.convert — is what stops that factor-of-100 from being lost in
a rounded intermediate.
:attr:FxRateSet.date is the date BNR published, which is not always the
date that was asked for: rates exist per banking day, so a Sunday, a holiday, or
a moment before the 13:00 publication resolves to the last published day. The
set carries the real date so a caller can say which day it actually got.
FxRate ¶
Bases: BaseModel
One currency's reference quotation, as BNR published it.
lei_per_unit
property
¶
lei_per_unit: Decimal
Lei for one unit — quoted with the multiplier applied.
Exact: the multiplier is always a power of ten, so the division adds digits rather than losing them.
FxRateSet ¶
Bases: BaseModel
Every reference rate BNR published on one banking day.
rate ¶
rate(currency: str) -> FxRate | None
The rate for currency, or None if BNR does not quote it.
convert ¶
convert(amount: Decimal | int | str, currency: str) -> Conversion
Convert amount of currency into RON at this day's rate.
The multiplier is applied and the result rounded to two decimals half away from zero — once, on the final figure. The base currency converts one-for-one (BNR quotes no rate for it).
Raises:
| Type | Description |
|---|---|
AnafConfigError
|
BNR does not quote currency on this day. The message lists what it does quote, so a wrong code self-heals. |
Conversion ¶
Bases: BaseModel
One amount converted to RON at a published BNR reference rate.