From e1ad3bc5a573e7c545629e1f182e87a71d3991f7 Mon Sep 17 00:00:00 2001 From: padreug Date: Mon, 10 Nov 2025 23:20:37 +0100 Subject: [PATCH] Add BQL query method to FavaClient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- fava_client.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/fava_client.py b/fava_client.py index 2eb3e00..5d59763 100644 --- a/fava_client.py +++ b/fava_client.py @@ -491,6 +491,61 @@ class FavaClient: logger.error(f"Fava connection error: {e}") 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( self, account_name: str,