Add BQL query method to FavaClient

Implemented query_bql() method to enable efficient Beancount Query Language
(BQL) queries against Fava API. This is the foundation for replacing manual
balance aggregation (115 lines) with optimized BQL queries.

Benefits:
- Efficient server-side filtering and aggregation
- 5-10x expected performance improvement
- Cleaner, more maintainable code

Next: Implement get_user_balance_bql() using this method.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
padreug 2025-11-10 23:20:37 +01:00
parent 6d6ac190c7
commit e1ad3bc5a5

View file

@ -491,6 +491,61 @@ class FavaClient:
logger.error(f"Fava connection error: {e}") logger.error(f"Fava connection error: {e}")
raise raise
async def query_bql(self, query_string: str) -> Dict[str, Any]:
"""
Execute arbitrary Beancount Query Language (BQL) query.
This is a general-purpose method for executing BQL queries against Fava/Beancount.
Use this for efficient aggregations, filtering, and data retrieval.
Args:
query_string: BQL query (e.g., "SELECT account, sum(position) WHERE account ~ 'User-abc'")
Returns:
{
"rows": [[col1, col2, ...], ...],
"types": [{"name": "col1", "type": "str"}, ...],
"column_names": ["col1", "col2", ...]
}
Example:
result = await fava.query_bql("SELECT account, sum(position) WHERE account ~ 'User-abc'")
for row in result["rows"]:
account, balance = row
print(f"{account}: {balance}")
See:
https://beancount.github.io/docs/beancount_query_language.html
"""
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{self.base_url}/query",
params={"query_string": query_string}
)
response.raise_for_status()
result = response.json()
# Fava returns: {"data": {"rows": [...], "types": [...]}}
data = result.get("data", {})
rows = data.get("rows", [])
types = data.get("types", [])
column_names = [t.get("name") for t in types]
return {
"rows": rows,
"types": types,
"column_names": column_names
}
except httpx.HTTPStatusError as e:
logger.error(f"BQL query error: {e.response.status_code} - {e.response.text}")
logger.error(f"Query was: {query_string}")
raise
except httpx.RequestError as e:
logger.error(f"Fava connection error: {e}")
raise
async def get_account_transactions( async def get_account_transactions(
self, self,
account_name: str, account_name: str,