From 8e962c0c59980a7382d9282d2a863ed05e148b1c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Dec 2021 08:03:19 +0100 Subject: [PATCH 01/24] add mixin to consum a single API --- .../plugin/builtin/integration/mixins.py | 72 +++++++++++++++++++ InvenTree/plugin/mixins/__init__.py | 4 +- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 3a6b558db7..70345172ce 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -1,4 +1,7 @@ """default mixins for IntegrationMixins""" +import json +import requests + from django.conf.urls import url, include from plugin.urls import PLUGIN_BASE @@ -167,3 +170,72 @@ class AppMixin: this plugin is always an app with this plugin """ return True + + +class APICallMixin: + """Mixin that enables easier API calls for a plugin + + 1. Add this mixin + 2. Add two global settings for the required url and token/passowrd (use `GlobalSettingsMixin`) + 3. Save the references to `API_URL_SETTING` and `API_PASSWORD_SETTING` + 4. Set `API_TOKEN` to the name required for the token / password by the external API + 5. (Optional) Override the `api_url` property method if some part of the APIs url is static + 6. (Optional) Override `api_headers` to add extra headers (by default the token/password and Content-Type are contained) + 6. Access the API in you plugin code via `api_call` + """ + API_METHOD = 'https' + API_URL_SETTING = None + API_PASSWORD_SETTING = None + + API_TOKEN = 'Bearer' + + class MixinMeta: + """meta options for this mixin""" + MIXIN_NAME = 'external API usage' + + def __init__(self): + super().__init__() + self.add_mixin('api_call', 'has_api_call', __class__) + + @property + def has_api_call(self): + """Is the mixin ready to call external APIs?""" + # TODO check if settings are set + return True + + @property + def api_url(self): + return f'{self.API_METHOD}://{self.get_globalsetting(self.API_URL_SETTING)}' + + @property + def api_headers(self): + return {self.API_TOKEN: self.get_globalsetting(self.API_PASSWORD_SETTING), 'Content-Type': 'application/json'} + + def api_build_url_args(self, arguments): + groups = [] + for key, val in arguments.items(): + groups.append(f'{key}={",".join([str(a) for a in val])}') + return f'?{"&".join(groups)}' + + def api_call(self, endpoint, method: str='GET', url_args=None, data=None, headers=None, simple_response: bool = True): + if url_args: + endpoint += self.api_build_url_args(url_args) + + if headers is None: + headers = self.api_headers + + # build kwargs for call + kwargs = { + 'url': f'{self.api_url}/{endpoint}', + 'headers': headers, + } + if data: + kwargs['data'] = json.dumps(data) + + # run command + response = requests.request(method, **kwargs) + + # return + if simple_response: + return response.json() + return response diff --git a/InvenTree/plugin/mixins/__init__.py b/InvenTree/plugin/mixins/__init__.py index feb6bc3466..d63bae097b 100644 --- a/InvenTree/plugin/mixins/__init__.py +++ b/InvenTree/plugin/mixins/__init__.py @@ -1,6 +1,6 @@ """utility class to enable simpler imports""" -from ..builtin.integration.mixins import AppMixin, GlobalSettingsMixin, UrlsMixin, NavigationMixin +from ..builtin.integration.mixins import AppMixin, GlobalSettingsMixin, UrlsMixin, NavigationMixin, APICallMixin __all__ = [ - 'AppMixin', 'GlobalSettingsMixin', 'UrlsMixin', 'NavigationMixin', + 'AppMixin', 'GlobalSettingsMixin', 'UrlsMixin', 'NavigationMixin', 'APICallMixin', ] From 251fdeb69e161d1c03fb7751fcdab61c49319471 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Dec 2021 18:01:20 +0100 Subject: [PATCH 02/24] PEP fixes --- InvenTree/plugin/builtin/integration/mixins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 70345172ce..932588c281 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -174,7 +174,7 @@ class AppMixin: class APICallMixin: """Mixin that enables easier API calls for a plugin - + 1. Add this mixin 2. Add two global settings for the required url and token/passowrd (use `GlobalSettingsMixin`) 3. Save the references to `API_URL_SETTING` and `API_PASSWORD_SETTING` @@ -217,7 +217,7 @@ class APICallMixin: groups.append(f'{key}={",".join([str(a) for a in val])}') return f'?{"&".join(groups)}' - def api_call(self, endpoint, method: str='GET', url_args=None, data=None, headers=None, simple_response: bool = True): + def api_call(self, endpoint, method: str = 'GET', url_args=None, data=None, headers=None, simple_response: bool = True): if url_args: endpoint += self.api_build_url_args(url_args) From a2871ccb45beebf61d54ed30df91722646fa600d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Dec 2021 18:40:24 +0100 Subject: [PATCH 03/24] update database images before running --- .github/workflows/qc_checks.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index 929a299e93..4491ba7815 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -282,6 +282,7 @@ jobs: cache: 'pip' - name: Install Dependencies run: | + sudo apt-get update sudo apt-get install mysql-server libmysqlclient-dev pip3 install invoke pip3 install mysqlclient From 62394c4a826b06eaf37dfea8e90cc21021f507e8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Jan 2022 21:54:42 +0100 Subject: [PATCH 04/24] small reformat --- InvenTree/plugin/builtin/integration/mixins.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 457f86fb80..4f2d35268e 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -342,7 +342,10 @@ class APICallMixin: @property def api_headers(self): - return {self.API_TOKEN: self.get_globalsetting(self.API_PASSWORD_SETTING), 'Content-Type': 'application/json'} + return { + self.API_TOKEN: self.get_globalsetting(self.API_PASSWORD_SETTING), + 'Content-Type': 'application/json' + } def api_build_url_args(self, arguments): groups = [] From f59b59401fb66374a60555308b249411750b205e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Jan 2022 21:58:44 +0100 Subject: [PATCH 05/24] refactor setting --- InvenTree/plugin/builtin/integration/mixins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 4f2d35268e..590c7615d6 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -318,7 +318,7 @@ class APICallMixin: """ API_METHOD = 'https' API_URL_SETTING = None - API_PASSWORD_SETTING = None + API_TOKEN_SETTING = None API_TOKEN = 'Bearer' @@ -343,7 +343,7 @@ class APICallMixin: @property def api_headers(self): return { - self.API_TOKEN: self.get_globalsetting(self.API_PASSWORD_SETTING), + self.API_TOKEN: self.get_globalsetting(self.API_TOKEN_SETTING), 'Content-Type': 'application/json' } From 3aea1bb7ba1c44c813ba8cf099d2ac43b91c5f62 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Jan 2022 21:59:02 +0100 Subject: [PATCH 06/24] made docstring clearer --- InvenTree/plugin/builtin/integration/mixins.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 590c7615d6..4001a100e3 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -306,14 +306,15 @@ class AppMixin: class APICallMixin: - """Mixin that enables easier API calls for a plugin + """ + Mixin that enables easier API calls for a plugin - 1. Add this mixin - 2. Add two global settings for the required url and token/passowrd (use `GlobalSettingsMixin`) - 3. Save the references to `API_URL_SETTING` and `API_PASSWORD_SETTING` - 4. Set `API_TOKEN` to the name required for the token / password by the external API - 5. (Optional) Override the `api_url` property method if some part of the APIs url is static - 6. (Optional) Override `api_headers` to add extra headers (by default the token/password and Content-Type are contained) + 1. Add this mixin before (left of) SettingsMixin and PluginBase + 2. Add two settings for the required url and token/passowrd (use `SettingsMixin`) + 3. Save the references to keys of the settings in `API_URL_SETTING` and `API_TOKEN_SETTING` + 4. (Optional) Set `API_TOKEN` to the name required for the token by the external API - Defaults to `Bearer` + 5. (Optional) Override the `api_url` property method if the setting needs to be extended + 6. (Optional) Override `api_headers` to add extra headers (by default the token and Content-Type are contained) 6. Access the API in you plugin code via `api_call` """ API_METHOD = 'https' From d939107d3633f623d76e83f140ff1a9852bee84b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:01:31 +0100 Subject: [PATCH 07/24] add example --- .../plugin/builtin/integration/mixins.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 4001a100e3..84d99d1bce 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -315,7 +315,40 @@ class APICallMixin: 4. (Optional) Set `API_TOKEN` to the name required for the token by the external API - Defaults to `Bearer` 5. (Optional) Override the `api_url` property method if the setting needs to be extended 6. (Optional) Override `api_headers` to add extra headers (by default the token and Content-Type are contained) - 6. Access the API in you plugin code via `api_call` + 7. Access the API in you plugin code via `api_call` + + Example: + ``` + from plugin import IntegrationPluginBase + from plugin.mixins import APICallMixin, SettingsMixin + + + class SampleApiCallerPlugin(APICallMixin, SettingsMixin, IntegrationPluginBase): + ''' + A small api call sample + ''' + PLUGIN_NAME = "Sample API Caller" + + SETTINGS = { + 'API_TOKEN': { + 'name': 'API Token', + 'protected': True, + }, + 'API_URL': { + 'name': 'External URL', + 'description': 'Where is your API located?', + 'default': 'https://reqres.in', + }, + } + API_URL_SETTING = 'API_URL' + API_TOKEN_SETTING = 'API_TOKEN' + + def get_external_url(self): + ''' + returns data from the sample endpoint + ''' + return self.api_call('api/users/2') + ``` """ API_METHOD = 'https' API_URL_SETTING = None From 33ee7e53dbbd861d32221b31969fa80d61a93b9c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:01:50 +0100 Subject: [PATCH 08/24] append docstring --- InvenTree/plugin/builtin/integration/mixins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 84d99d1bce..d9f6bdc2d5 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -309,6 +309,7 @@ class APICallMixin: """ Mixin that enables easier API calls for a plugin + Steps to set up: 1. Add this mixin before (left of) SettingsMixin and PluginBase 2. Add two settings for the required url and token/passowrd (use `SettingsMixin`) 3. Save the references to keys of the settings in `API_URL_SETTING` and `API_TOKEN_SETTING` From 19f2c44c2a77e89bd508b99ceea45a2ce9233344 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:02:19 +0100 Subject: [PATCH 09/24] change mixin name --- InvenTree/plugin/builtin/integration/mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index d9f6bdc2d5..22ba6de27d 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -359,7 +359,7 @@ class APICallMixin: class MixinMeta: """meta options for this mixin""" - MIXIN_NAME = 'external API usage' + MIXIN_NAME = 'API calls' def __init__(self): super().__init__() From 61b21d1ec14e0be683f8da2b92b3ca2aa9fdcf59 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:03:05 +0100 Subject: [PATCH 10/24] add sample for api caller --- .../plugin/samples/integration/api_caller.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 InvenTree/plugin/samples/integration/api_caller.py diff --git a/InvenTree/plugin/samples/integration/api_caller.py b/InvenTree/plugin/samples/integration/api_caller.py new file mode 100644 index 0000000000..eaef93a4b6 --- /dev/null +++ b/InvenTree/plugin/samples/integration/api_caller.py @@ -0,0 +1,34 @@ +""" +Sample plugin for calling an external API +""" +from django.utils.translation import ugettext_lazy as _ + +from plugin import IntegrationPluginBase +from plugin.mixins import APICallMixin, SettingsMixin + + +class SampleApiCallerPlugin(APICallMixin, SettingsMixin, IntegrationPluginBase): + """ + A small api call sample + """ + PLUGIN_NAME = "Sample API Caller" + + SETTINGS = { + 'API_TOKEN': { + 'name': 'API Token', + 'protected': True, + }, + 'API_URL': { + 'name': 'External URL', + 'description': 'Where is your API located?', + 'default': 'https://reqres.in', + }, + } + API_URL_SETTING = 'API_URL' + API_TOKEN_SETTING = 'API_TOKEN' + + def get_external_url(self): + """ + returns data from the sample endpoint + """ + return self.api_call('api/users/2') From ed193e9e90304c04ed8047a2ffc41f8926808e0e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:04:00 +0100 Subject: [PATCH 11/24] docstring for plugin base import class --- InvenTree/plugin/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InvenTree/plugin/__init__.py b/InvenTree/plugin/__init__.py index b3dc3a2fd0..86f65919c4 100644 --- a/InvenTree/plugin/__init__.py +++ b/InvenTree/plugin/__init__.py @@ -1,3 +1,7 @@ +""" +Utility file to enable simper imports +""" + from .registry import plugin_registry from .plugin import InvenTreePlugin from .integration import IntegrationPluginBase From ea8fd21af09933526a5930cce7c70b7e2b69eda4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:10:23 +0100 Subject: [PATCH 12/24] pip fix --- InvenTree/plugin/samples/integration/api_caller.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/InvenTree/plugin/samples/integration/api_caller.py b/InvenTree/plugin/samples/integration/api_caller.py index eaef93a4b6..7e5c883961 100644 --- a/InvenTree/plugin/samples/integration/api_caller.py +++ b/InvenTree/plugin/samples/integration/api_caller.py @@ -1,8 +1,6 @@ """ Sample plugin for calling an external API """ -from django.utils.translation import ugettext_lazy as _ - from plugin import IntegrationPluginBase from plugin.mixins import APICallMixin, SettingsMixin From b48e9bcac9cf8417cf51ba44f3f0577d2cc1fa89 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:33:47 +0100 Subject: [PATCH 13/24] fix settings call --- InvenTree/plugin/builtin/integration/mixins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 22ba6de27d..c8bb9f7f9e 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -373,12 +373,12 @@ class APICallMixin: @property def api_url(self): - return f'{self.API_METHOD}://{self.get_globalsetting(self.API_URL_SETTING)}' + return f'{self.API_METHOD}://{self.get_setting(self.API_URL_SETTING)}' @property def api_headers(self): return { - self.API_TOKEN: self.get_globalsetting(self.API_TOKEN_SETTING), + self.API_TOKEN: self.get_setting(self.API_TOKEN_SETTING), 'Content-Type': 'application/json' } From cc8948c708cdb00c28341e42c7b321f0ffa4d37b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:34:08 +0100 Subject: [PATCH 14/24] fix sample url --- InvenTree/plugin/builtin/integration/mixins.py | 2 +- InvenTree/plugin/samples/integration/api_caller.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index c8bb9f7f9e..66676d0520 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -338,7 +338,7 @@ class APICallMixin: 'API_URL': { 'name': 'External URL', 'description': 'Where is your API located?', - 'default': 'https://reqres.in', + 'default': 'reqres.in', }, } API_URL_SETTING = 'API_URL' diff --git a/InvenTree/plugin/samples/integration/api_caller.py b/InvenTree/plugin/samples/integration/api_caller.py index 7e5c883961..36e1583ba0 100644 --- a/InvenTree/plugin/samples/integration/api_caller.py +++ b/InvenTree/plugin/samples/integration/api_caller.py @@ -19,7 +19,7 @@ class SampleApiCallerPlugin(APICallMixin, SettingsMixin, IntegrationPluginBase): 'API_URL': { 'name': 'External URL', 'description': 'Where is your API located?', - 'default': 'https://reqres.in', + 'default': 'reqres.in', }, } API_URL_SETTING = 'API_URL' From f9742ab41d6f75d44f853b7767b37f30fb289512 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:34:27 +0100 Subject: [PATCH 15/24] add integration test for plugin --- .../samples/integration/test_api_caller.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 InvenTree/plugin/samples/integration/test_api_caller.py diff --git a/InvenTree/plugin/samples/integration/test_api_caller.py b/InvenTree/plugin/samples/integration/test_api_caller.py new file mode 100644 index 0000000000..7db431c3ee --- /dev/null +++ b/InvenTree/plugin/samples/integration/test_api_caller.py @@ -0,0 +1,20 @@ +""" Unit tests for action caller sample""" + +from django.test import TestCase + +from plugin import plugin_registry + +class SampleApiCallerPluginTests(TestCase): + """ Tests for SampleApiCallerPluginTests """ + + def test_return(self): + """check if the external api call works""" + # The plugin should be defined + self.assertIn('sample-api-caller', plugin_registry.plugins) + plg = plugin_registry.plugins['sample-api-caller'] + self.assertTrue(plg) + + # do an api call + result = plg.get_external_url() + self.assertTrue(result) + self.assertIn('data', result,) From ad9a9da656f7686dde4bd23133e2ea7b4cf63c50 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 03:35:29 +0100 Subject: [PATCH 16/24] PEP fix --- InvenTree/plugin/samples/integration/test_api_caller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/plugin/samples/integration/test_api_caller.py b/InvenTree/plugin/samples/integration/test_api_caller.py index 7db431c3ee..e15edfad94 100644 --- a/InvenTree/plugin/samples/integration/test_api_caller.py +++ b/InvenTree/plugin/samples/integration/test_api_caller.py @@ -4,6 +4,7 @@ from django.test import TestCase from plugin import plugin_registry + class SampleApiCallerPluginTests(TestCase): """ Tests for SampleApiCallerPluginTests """ From c699ced34ad263fdb0cdc6372d73fa2e5a7ac7a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 22:16:19 +0100 Subject: [PATCH 17/24] make general mixin tests multi mixin enabled --- InvenTree/plugin/test_integration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/test_integration.py b/InvenTree/plugin/test_integration.py index 3d88fed4dd..abddc9ee59 100644 --- a/InvenTree/plugin/test_integration.py +++ b/InvenTree/plugin/test_integration.py @@ -15,9 +15,9 @@ from plugin.urls import PLUGIN_BASE class BaseMixinDefinition: def test_mixin_name(self): # mixin name - self.assertEqual(self.mixin.registered_mixins[0]['key'], self.MIXIN_NAME) + self.assertIn(self.MIXIN_NAME, [item['key'] for item in self.mixin.registered_mixins]) # human name - self.assertEqual(self.mixin.registered_mixins[0]['human_name'], self.MIXIN_HUMAN_NAME) + self.assertIn(self.MIXIN_HUMAN_NAME, [item['human_name'] for item in self.mixin.registered_mixins]) class SettingsMixinTest(BaseMixinDefinition, TestCase): From 31d587a9b190ef7eb0c7546fa6e4ed25e477da3c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 22:19:01 +0100 Subject: [PATCH 18/24] unittests fdor ApiCallMixin --- InvenTree/plugin/test_integration.py | 46 +++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/test_integration.py b/InvenTree/plugin/test_integration.py index abddc9ee59..eb55f984d3 100644 --- a/InvenTree/plugin/test_integration.py +++ b/InvenTree/plugin/test_integration.py @@ -8,7 +8,7 @@ from django.contrib.auth import get_user_model from datetime import datetime from plugin import IntegrationPluginBase -from plugin.mixins import AppMixin, SettingsMixin, UrlsMixin, NavigationMixin +from plugin.mixins import AppMixin, SettingsMixin, UrlsMixin, NavigationMixin, APICallMixin from plugin.urls import PLUGIN_BASE @@ -142,6 +142,50 @@ class NavigationMixinTest(BaseMixinDefinition, TestCase): self.assertEqual(self.nothing_mixin.navigation_name, '') +class APICallMixinTest(BaseMixinDefinition, TestCase): + MIXIN_HUMAN_NAME = 'API calls' + MIXIN_NAME = 'api_call' + MIXIN_ENABLE_CHECK = 'has_api_call' + + def setUp(self): + class MixinCls(APICallMixin, SettingsMixin, IntegrationPluginBase): + PLUGIN_NAME = "Sample API Caller" + + SETTINGS = { + 'API_TOKEN': { + 'name': 'API Token', + 'protected': True, + }, + 'API_URL': { + 'name': 'External URL', + 'description': 'Where is your API located?', + 'default': 'reqres.in', + }, + } + API_URL_SETTING = 'API_URL' + API_TOKEN_SETTING = 'API_TOKEN' + + def get_external_url(self): + ''' + returns data from the sample endpoint + ''' + return self.api_call('api/users/2') + self.mixin = MixinCls() + def test_function(self): + # api_url + self.assertEqual('https://reqres.in', self.mixin.api_url) + + # api_headers + headers = self.mixin.api_headers + self.assertEqual(headers, {'Bearer': '', 'Content-Type': 'application/json'}) + + # api_build_url_args + # api_call + result = self.mixin.get_external_url() + self.assertTrue(result) + self.assertIn('data', result,) + + class IntegrationPluginBaseTests(TestCase): """ Tests for IntegrationPluginBase """ From e889f487f010be2b59a0961e051ce93e54db40de Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 22:27:50 +0100 Subject: [PATCH 19/24] added a check for the required constants --- InvenTree/plugin/builtin/integration/mixins.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 66676d0520..7d257dbee5 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -368,7 +368,10 @@ class APICallMixin: @property def has_api_call(self): """Is the mixin ready to call external APIs?""" - # TODO check if settings are set + if not bool(self.API_URL_SETTING): + raise ValueError("API_URL_SETTING must be defined") + if not bool(self.API_TOKEN_SETTING): + raise ValueError("API_TOKEN_SETTING must be defined") return True @property From c8599039a25cb17535f41149d588ff3acb4836af Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 22:33:14 +0100 Subject: [PATCH 20/24] added test for wrong config --- InvenTree/plugin/test_integration.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/InvenTree/plugin/test_integration.py b/InvenTree/plugin/test_integration.py index eb55f984d3..48533a70e2 100644 --- a/InvenTree/plugin/test_integration.py +++ b/InvenTree/plugin/test_integration.py @@ -171,6 +171,11 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): ''' return self.api_call('api/users/2') self.mixin = MixinCls() + + class WrongCLS(APICallMixin, IntegrationPluginBase): + pass + self.mixin_nothing = WrongCLS() + def test_function(self): # api_url self.assertEqual('https://reqres.in', self.mixin.api_url) @@ -185,6 +190,10 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): self.assertTrue(result) self.assertIn('data', result,) + # wrongly defined plugins should not load + with self.assertRaises(ValueError): + self.mixin_nothing.has_api_call() + class IntegrationPluginBaseTests(TestCase): """ Tests for IntegrationPluginBase """ From 2c05b858a4afa2542a20556b6a5f3b6c8ea6f9e0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 22:34:02 +0100 Subject: [PATCH 21/24] renmae var --- InvenTree/plugin/test_integration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/test_integration.py b/InvenTree/plugin/test_integration.py index 48533a70e2..a65fc85708 100644 --- a/InvenTree/plugin/test_integration.py +++ b/InvenTree/plugin/test_integration.py @@ -174,7 +174,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): class WrongCLS(APICallMixin, IntegrationPluginBase): pass - self.mixin_nothing = WrongCLS() + self.mixin_wrong = WrongCLS() def test_function(self): # api_url @@ -192,7 +192,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): # wrongly defined plugins should not load with self.assertRaises(ValueError): - self.mixin_nothing.has_api_call() + self.mixin_wrong.has_api_call() class IntegrationPluginBaseTests(TestCase): From afada6b7598787f5fd27de1a186bbd390f7af923 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 22:56:14 +0100 Subject: [PATCH 22/24] test the url arg building --- InvenTree/plugin/test_integration.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/InvenTree/plugin/test_integration.py b/InvenTree/plugin/test_integration.py index a65fc85708..5831e50af2 100644 --- a/InvenTree/plugin/test_integration.py +++ b/InvenTree/plugin/test_integration.py @@ -185,6 +185,16 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): self.assertEqual(headers, {'Bearer': '', 'Content-Type': 'application/json'}) # api_build_url_args + # 1 arg + result = self.mixin.api_build_url_args({'a': 'b'}) + self.assertEqual(result, '?a=b') + # more args + result = self.mixin.api_build_url_args({'a': 'b', 'c': 'd'}) + self.assertEqual(result, '?a=b&c=d') + # list args + result = self.mixin.api_build_url_args({'a': 'b', 'c': ['d', 'e', 'f', ]}) + self.assertEqual(result, '?a=b&c=d,e,f') + # api_call result = self.mixin.get_external_url() self.assertTrue(result) From bf7af8f72ab64956396e358df9dc20e2ef4ea0ee Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 23:01:38 +0100 Subject: [PATCH 23/24] cover another missing setting --- InvenTree/plugin/test_integration.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/InvenTree/plugin/test_integration.py b/InvenTree/plugin/test_integration.py index 5831e50af2..7ce581e68b 100644 --- a/InvenTree/plugin/test_integration.py +++ b/InvenTree/plugin/test_integration.py @@ -176,7 +176,13 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): pass self.mixin_wrong = WrongCLS() + class WrongCLS2(APICallMixin, IntegrationPluginBase): + API_URL_SETTING = 'test' + self.mixin_wrong2 = WrongCLS2() + def test_function(self): + # check init + self.assertTrue(self.mixin.has_api_call()) # api_url self.assertEqual('https://reqres.in', self.mixin.api_url) @@ -204,6 +210,10 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): with self.assertRaises(ValueError): self.mixin_wrong.has_api_call() + # cover wrong token setting + with self.assertRaises(ValueError): + self.mixin_wrong.has_api_call() + class IntegrationPluginBaseTests(TestCase): """ Tests for IntegrationPluginBase """ From 6af2267e3df77509b726158d189079f7ceb9d024 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Jan 2022 23:05:51 +0100 Subject: [PATCH 24/24] fix test --- InvenTree/plugin/test_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/test_integration.py b/InvenTree/plugin/test_integration.py index 7ce581e68b..dbc77f7cd0 100644 --- a/InvenTree/plugin/test_integration.py +++ b/InvenTree/plugin/test_integration.py @@ -182,7 +182,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): def test_function(self): # check init - self.assertTrue(self.mixin.has_api_call()) + self.assertTrue(self.mixin.has_api_call) # api_url self.assertEqual('https://reqres.in', self.mixin.api_url)