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

1"""Builtin plugin for requesting exchange rates from an external API.""" 

2 

3from django.utils.translation import gettext_lazy as _ 

4 

5import structlog 

6 

7from plugin import InvenTreePlugin 

8from plugin.mixins import APICallMixin, CurrencyExchangeMixin 

9 

10logger = structlog.get_logger('inventree') 

11 

12 

13class InvenTreeCurrencyExchange(APICallMixin, CurrencyExchangeMixin, InvenTreePlugin): 

14 """Default InvenTree plugin for currency exchange rates. 

15 

16 Fetches exchange rate information from frankfurter.app 

17 """ 

18 

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' 

25 

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 ) 

33 

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 

37 

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 {} 

45 

46 @property 

47 def api_url(self): 

48 """Return the API URL for this plugin.""" 

49 return 'https://api.frankfurter.app'