Skip to content

Commit 1ecc48e

Browse files
wilsonfreitasclaude
andcommitted
Phase 5: API Consistency
- URL construction: _currency_url() now uses urllib.parse.urlencode instead of f-string query params for better consistency and maintainability - OData API: Endpoint.get() now accepts explicit typed kwargs for filter, orderby, select, limit, skip, verbose, and output; maintains backwards compatibility with positional *args dispatch - All validation checks passing: 24 tests, ruff format/check, mypy Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent edc68ca commit 1ecc48e

2 files changed

Lines changed: 57 additions & 22 deletions

File tree

bcb/currency.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from datetime import date, timedelta
66
from io import BytesIO, StringIO
77
from typing import TYPE_CHECKING, Dict, List, Literal, NamedTuple, Union, overload
8+
from urllib.parse import urlencode
89

910
import numpy as np
1011
import pandas as pd
@@ -30,11 +31,15 @@
3031
def _currency_url(currency_id: int, start_date: DateInput, end_date: DateInput) -> str:
3132
start_date = Date(start_date)
3233
end_date = Date(end_date)
33-
return (
34-
f"https://ptax.bcb.gov.br/ptax_internet/consultaBoletim.do?"
35-
f"method=gerarCSVFechamentoMoedaNoPeriodo&"
36-
f"ChkMoeda={currency_id}&DATAINI={start_date.date:%d/%m/%Y}&DATAFIM={end_date.date:%d/%m/%Y}"
34+
params = urlencode(
35+
{
36+
"method": "gerarCSVFechamentoMoedaNoPeriodo",
37+
"ChkMoeda": currency_id,
38+
"DATAINI": start_date.date.strftime("%d/%m/%Y"),
39+
"DATAFIM": end_date.date.strftime("%d/%m/%Y"),
40+
}
3741
)
42+
return f"https://ptax.bcb.gov.br/ptax_internet/consultaBoletim.do?{params}"
3843

3944

4045
class _CacheKey(NamedTuple):

bcb/odata/api.py

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,48 +120,78 @@ def __init__(
120120
self._url = url
121121
self._date_columns: list[str] = date_columns or []
122122

123-
def get(self, *args: Any, **kwargs: Any) -> Union[pd.DataFrame, str]:
123+
def get(
124+
self,
125+
*args: Any,
126+
filter: Optional[ODataPropertyFilter] = None,
127+
orderby: Optional[ODataPropertyOrderBy] = None,
128+
select: Optional[ODataProperty] = None,
129+
limit: Optional[int] = None,
130+
skip: Optional[int] = None,
131+
output: str = "dataframe",
132+
verbose: bool = False,
133+
**kwargs: Any,
134+
) -> Union[pd.DataFrame, str]:
124135
"""
125136
Executa a consulta na API OData e retorna o resultado.
126137
127138
Parameters
128139
----------
129-
*args : argumentos para a consulta
130-
131-
**kwargs : argumentos para a consulta. Use ``output='text'`` to get
132-
the raw OData JSON response string instead of a DataFrame.
140+
*args : argumentos para a consulta (ODataPropertyFilter, ODataPropertyOrderBy, ODataProperty)
141+
filter : ODataPropertyFilter, optional
142+
Filter condition for the query
143+
orderby : ODataPropertyOrderBy, optional
144+
Order by condition for the query
145+
select : ODataProperty, optional
146+
Properties to select from the query
147+
limit : int, optional
148+
Limit the number of results
149+
skip : int, optional
150+
Skip the first N results
151+
output : str, default "dataframe"
152+
Output format. Use ``'text'`` to get the raw OData JSON response
153+
string instead of a DataFrame.
154+
verbose : bool, default False
155+
Print the query before executing it
156+
**kwargs : argumentos adicionais para a consulta
133157
134158
Returns
135159
-------
136160
pd.DataFrame or str: resultado da consulta. Returns a DataFrame by
137161
default; returns a raw JSON string when ``output='text'``.
138162
"""
139163
_query = EndpointQuery(self._entity, self._url, self._date_columns)
164+
165+
# Apply explicit kwargs first
166+
if filter is not None:
167+
_query.filter(filter)
168+
if orderby is not None:
169+
_query.orderby(orderby)
170+
if select is not None:
171+
_query.select(select)
172+
if limit is not None:
173+
_query.limit(limit)
174+
if skip is not None:
175+
_query.skip(skip)
176+
177+
# Apply positional args for backwards compatibility
140178
for arg in args:
141179
if isinstance(arg, ODataPropertyFilter):
142180
_query.filter(arg)
143181
elif isinstance(arg, ODataPropertyOrderBy):
144182
_query.orderby(arg)
145183
elif isinstance(arg, ODataProperty):
146184
_query.select(arg)
147-
verbose = False
148-
output_format = "dataframe"
185+
186+
# Apply any remaining kwargs as query parameters
149187
for k, val in kwargs.items():
150-
if k == "limit":
151-
_query.limit(val)
152-
elif k == "skip":
153-
_query.skip(val)
154-
elif k == "verbose":
155-
verbose = val
156-
elif k == "output":
157-
output_format = val
158-
else:
159-
_query.parameters(**{k: val})
188+
_query.parameters(**{k: val})
189+
160190
_query.format("application/json")
161191

162192
if verbose:
163193
_query.show()
164-
if output_format == "text":
194+
if output == "text":
165195
data = _query.collect(output="text")
166196
else:
167197
data = _query.collect()

0 commit comments

Comments
 (0)