Coverage for src/backend/InvenTree/plugin/builtin/integration/currency_exchange.py: 88%
23 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""Builtin plugin for requesting exchange rates from an external API."""
3from django.utils.translation import gettext_lazy as _
5import structlog
7from plugin import InvenTreePlugin
8from plugin.mixins import APICallMixin, CurrencyExchangeMixin
10logger = structlog.get_logger('inventree')
13class InvenTreeCurrencyExchange(APICallMixin, CurrencyExchangeMixin, InvenTreePlugin):
14 """Default InvenTree plugin for currency exchange rates.
16 Fetches exchange rate information from frankfurter.app
17 """
19 NAME = 'InvenTreeCurrencyExchange'
20 SLUG = 'inventreecurrencyexchange'
21 AUTHOR = _('InvenTree contributors')
22 TITLE = _('InvenTree Currency Exchange')
23 DESCRIPTION = _('Default currency exchange integration')
24 VERSION = '1.0.0'
26 def update_exchange_rates(self, base_currency: str, symbols: list[str]) -> dict:
27 """Request exchange rate data from external API."""
28 response = self.api_call(
29 'latest',
30 url_args={'from': [base_currency], 'to': symbols},
31 simple_response=False,
32 )
34 if response.status_code == 200: 34 ↛ 39line 34 didn't jump to line 39 because the condition on line 34 was always true
35 rates = response.json().get('rates', {})
36 rates[base_currency] = 1.00
38 return rates
39 logger.warning(
40 'Failed to update exchange rates from %s: Server returned status %s',
41 self.api_url,
42 response.status_code,
43 )
44 return {}
46 @property
47 def api_url(self):
48 """Return the API URL for this plugin."""
49 return 'https://api.frankfurter.app'