From 4c1988c957945bc0bf364766531aa3b3b6629052 Mon Sep 17 00:00:00 2001 From: wxt9861 <34514130+wxt9861@users.noreply.github.com> Date: Tue, 8 Aug 2023 18:03:25 +0000 Subject: [PATCH 1/4] chore: Update dockerfile python version --- .devcontainer/Dockerfile | 2 +- .vscode/settings.json | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 86d218f..d207f37 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10 +FROM python:3.11 RUN apt-get update \ && apt-get install -y --no-install-recommends \ diff --git a/.vscode/settings.json b/.vscode/settings.json index 988937c..ccc25c9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "python.pythonPath": "/usr/local/bin/python3" + "python.pythonPath": "/usr/local/bin/python3", + "files.associations": { + "*.yaml": "home-assistant" + } } \ No newline at end of file From 4909425c92984ac7a509db67cf87ddb336af0379 Mon Sep 17 00:00:00 2001 From: wxt9861 <34514130+wxt9861@users.noreply.github.com> Date: Tue, 8 Aug 2023 18:03:42 +0000 Subject: [PATCH 2/4] chore: bump versions --- custom_components/ynab/const.py | 2 +- custom_components/ynab/manifest.json | 4 ++-- requirements.txt | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 requirements.txt diff --git a/custom_components/ynab/const.py b/custom_components/ynab/const.py index e11a97d..d4c4cfb 100644 --- a/custom_components/ynab/const.py +++ b/custom_components/ynab/const.py @@ -4,7 +4,7 @@ PLATFORMS = ["sensor"] REQUIRED_FILES = ["const.py", "manifest.json", "sensor.py"] -VERSION = "0.2.0" +VERSION = "0.3.0" ISSUE_URL = "https://github.com/wxt9861/ynab/issues" STARTUP = """ diff --git a/custom_components/ynab/manifest.json b/custom_components/ynab/manifest.json index fb0e3cb..d10e119 100644 --- a/custom_components/ynab/manifest.json +++ b/custom_components/ynab/manifest.json @@ -6,6 +6,6 @@ "dependencies": [], "codeowners": ["@wxt9861"], "iot_class": "cloud_polling", - "requirements": ["ynab-sdk==0.4.0"], - "version": "0.2.0" + "requirements": ["ynab-sdk==0.5.0"], + "version": "0.3.0" } diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8165baf --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +ynab-sdk==0.5.0 \ No newline at end of file From 83bcd924813e8641898a00eb876130748472e242 Mon Sep 17 00:00:00 2001 From: wxt9861 <34514130+wxt9861@users.noreply.github.com> Date: Tue, 8 Aug 2023 18:09:38 +0000 Subject: [PATCH 3/4] feat: request transaction import on update --- custom_components/ynab/__init__.py | 42 +++++++++++++++++++++++++----- custom_components/ynab/const.py | 1 + 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/custom_components/ynab/__init__.py b/custom_components/ynab/__init__.py index 862af0d..79c78bb 100644 --- a/custom_components/ynab/__init__.py +++ b/custom_components/ynab/__init__.py @@ -1,22 +1,24 @@ """YNAB Integration.""" +import json import logging import os -from datetime import timedelta, date -from ynab_sdk import YNAB +from datetime import date, timedelta +import aiohttp import homeassistant.helpers.config_validation as cv +import voluptuous as vol from homeassistant.const import CONF_API_KEY from homeassistant.helpers import discovery from homeassistant.util import Throttle - -import voluptuous as vol +from ynab_sdk import YNAB from .const import ( CONF_NAME, - DEFAULT_NAME, + DEFAULT_API_ENDPOINT, DEFAULT_BUDGET, DEFAULT_CURRENCY, + DEFAULT_NAME, DOMAIN, DOMAIN_DATA, ISSUE_URL, @@ -115,6 +117,8 @@ async def update_data(self): """Update data.""" # setup YNAB API + await self.request_import() + self.ynab = YNAB(self.api_key) self.all_budgets = await self.hass.async_add_executor_job( self.ynab.budgets.get_budgets @@ -251,6 +255,31 @@ async def update_data(self): [category.name, category.balance / 1000, category.budgeted / 1000], ) + async def request_import(self): + """Force transaction import.""" + + import_endpoint = ( + f"{DEFAULT_API_ENDPOINT}/budgets/{self.budget}/transactions/import" + ) + headers = { + "accept": "application/json", + "Authorization": f"Bearer {self.api_key}", + } + + try: + async with aiohttp.ClientSession(headers=headers) as session: + async with session.post(url=import_endpoint) as response: + if response.status in [200, 201]: + response_data = json.loads(await response.text()) + + _LOGGER.debug( + "Imported transactions: %s", + len(response_data["data"]["transaction_ids"]), + ) + _LOGGER.debug("API Stats: %s", response.headers["X-Rate-Limit"]) + except Exception as error: # pylint: disable=broad-except + _LOGGER.debug("Error encounted during forced import - %s", error) + async def check_files(hass): """Return bool that indicates if all files are present.""" @@ -272,9 +301,8 @@ async def check_files(hass): async def check_url(): """Return bool that indicates YNAB URL is accessible.""" - import aiohttp # pylint: disable=import-outside-toplevel - url = "https://api.youneedabudget.com/v1" + url = DEFAULT_API_ENDPOINT try: async with aiohttp.ClientSession() as session: diff --git a/custom_components/ynab/const.py b/custom_components/ynab/const.py index d4c4cfb..21c365c 100644 --- a/custom_components/ynab/const.py +++ b/custom_components/ynab/const.py @@ -25,6 +25,7 @@ DEFAULT_NAME = "ynab" DEFAULT_BUDGET = "last-used" DEFAULT_CURRENCY = "$" +DEFAULT_API_ENDPOINT = "https://api.ynab.com/v1" ICON = "mdi:finance" From e3ec4518ff72f7c2eb1aa047508356fab53dd0ac Mon Sep 17 00:00:00 2001 From: wxt9861 <34514130+wxt9861@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:42:26 +0000 Subject: [PATCH 4/4] feat: Fire event when transactions imported --- custom_components/ynab/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/custom_components/ynab/__init__.py b/custom_components/ynab/__init__.py index 79c78bb..cd062a0 100644 --- a/custom_components/ynab/__init__.py +++ b/custom_components/ynab/__init__.py @@ -277,6 +277,16 @@ async def request_import(self): len(response_data["data"]["transaction_ids"]), ) _LOGGER.debug("API Stats: %s", response.headers["X-Rate-Limit"]) + + if len(response_data["data"]["transaction_ids"]) > 0: + _event_topic = DOMAIN + "_event" + _event_data = { + "transactions_imported": len( + response_data["data"]["transaction_ids"] + ) + } + self.hass.bus.async_fire(_event_topic, _event_data) + except Exception as error: # pylint: disable=broad-except _LOGGER.debug("Error encounted during forced import - %s", error)