diff --git a/pynautobot/core/app.py b/pynautobot/core/app.py index d260b63..149482e 100644 --- a/pynautobot/core/app.py +++ b/pynautobot/core/app.py @@ -15,10 +15,14 @@ This file has been modified by NetworktoCode, LLC. """ +import logging + from pynautobot.core.endpoint import Endpoint, JobsEndpoint from pynautobot.core.query import Request from pynautobot.models import circuits, dcim, extras, ipam, users, virtualization +logger = logging.getLogger(__name__) + class App(object): """Represents apps in Nautobot. @@ -85,6 +89,11 @@ def choices(self): def custom_choices(self): """Returns custom-fields response from app + .. note:: + + This method is deprecated and will be removed in pynautobot + 2.0 or newer. Please use `custom_fields()` instead. + :Returns: Raw response from Nautobot's custom-fields endpoint. :Raises: :py:class:`.RequestError` if called for an invalid endpoint. :Example: @@ -93,11 +102,81 @@ def custom_choices(self): {'Testfield1': {'Testvalue2': 2, 'Testvalue1': 1}, 'Testfield2': {'Othervalue2': 4, 'Othervalue1': 3}} """ + logger.warning( + "WARNING: The method 'custom_choices()' will be removed in " + "the next major version (2.x) of pynautobot. Please use " + "`custom_fields()` instead." + ) + + return self.custom_fields() + + def custom_fields(self): + """Returns custom-fields response from app + + :Returns: Raw response from Nautobot's custom-fields endpoint. + :Raises: :py:class:`.RequestError` if called for an invalid endpoint. + :Example: + + >>> nb.extras.custom_fields() + [ + { + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "display": "Test custom field", + "url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "content_types": ["dcim.rack"], + "type": {"value": "integer", "label": "Integer"}, + "label": "Test custom field", + "name": "test_custom_field", + "slug": "test_custom_field", + "description": "", + "required": False, + "filter_logic": {"value": "loose", "label": "Loose"}, + "default": None, + "weight": 100, + "validation_minimum": None, + "validation_maximum": None, + "validation_regex": "", + "created": "2023-04-15", + "last_updated": "2023-04-15T17:45:11.839431Z", + "notes_url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/notes/", + }, + ] + """ custom_fields = Request( - base="{}/{}/custom-fields/".format( - self.api.base_url, - self.name, - ), + base=f"{self.api.base_url}/{self.name}/custom-fields/", + token=self.api.token, + http_session=self.api.http_session, + ).get() + return custom_fields + + def custom_field_choices(self): + """Returns custom-field-choices response from app + + :Returns: Raw response from Nautobot's custom-field-choices endpoint. + :Raises: :py:class:`.RequestError` if called for an invalid endpoint. + :Example: + + >>> nb.extras.custom_field_choices() + [ + { + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "display": "First option", + "url": "http://localhost:8000/api/extras/custom-field-choices/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "field": { + "display": "Test custom field 2", + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "name": "test_custom_field_2" + }, + "value": "First option", + "weight": 100, + "created": "2023-04-15", + "last_updated": "2023-04-15T18:11:57.163237Z" + }, + ] + """ + custom_fields = Request( + base=f"{self.api.base_url}/{self.name}/custom-field-choices/", token=self.api.token, http_session=self.api.http_session, ).get() diff --git a/tests/fixtures/extras/custom_field_choices.json b/tests/fixtures/extras/custom_field_choices.json new file mode 100644 index 0000000..9f2239c --- /dev/null +++ b/tests/fixtures/extras/custom_field_choices.json @@ -0,0 +1,47 @@ +[ + { + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "display": "First option", + "url": "http://localhost:8000/api/extras/custom-field-choices/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "field": { + "display": "Test custom field 2", + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "name": "test_custom_field_2" + }, + "value": "First option", + "weight": 100, + "created": "2023-04-15", + "last_updated": "2023-04-15T18:11:57.163237Z" + }, + { + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "display": "Second option", + "url": "http://localhost:8000/api/extras/custom-field-choices/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "field": { + "display": "Test custom field 2", + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "name": "test_custom_field_2" + }, + "value": "Second option", + "weight": 100, + "created": "2023-04-15", + "last_updated": "2023-04-15T18:11:57.169962Z" + }, + { + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "display": "Third option", + "url": "http://localhost:8000/api/extras/custom-field-choices/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "field": { + "display": "Test custom field 2", + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "name": "test_custom_field_2" + }, + "value": "Third option", + "weight": 100, + "created": "2023-04-15", + "last_updated": "2023-04-15T18:11:57.174825Z" + } +] diff --git a/tests/fixtures/extras/custom_fields.json b/tests/fixtures/extras/custom_fields.json new file mode 100644 index 0000000..aacf67f --- /dev/null +++ b/tests/fixtures/extras/custom_fields.json @@ -0,0 +1,60 @@ +[ + { + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "display": "Test custom field", + "url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "content_types": [ + "dcim.rack" + ], + "type": { + "value": "integer", + "label": "Integer" + }, + "label": "Test custom field", + "name": "test_custom_field", + "slug": "test_custom_field", + "description": "", + "required": false, + "filter_logic": { + "value": "loose", + "label": "Loose" + }, + "default": null, + "weight": 100, + "validation_minimum": null, + "validation_maximum": null, + "validation_regex": "", + "created": "2023-04-15", + "last_updated": "2023-04-15T17:45:11.839431Z", + "notes_url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/notes/" + }, + { + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "display": "Test custom field 2", + "url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "content_types": [ + "dcim.rack" + ], + "type": { + "value": "select", + "label": "Selection" + }, + "label": "Test custom field 2", + "name": "test_custom_field_2", + "slug": "test_custom_field_2", + "description": "", + "required": false, + "filter_logic": { + "value": "loose", + "label": "Loose" + }, + "default": null, + "weight": 100, + "validation_minimum": null, + "validation_maximum": null, + "validation_regex": "", + "created": "2023-04-15", + "last_updated": "2023-04-15T18:11:57.133408Z", + "notes_url": "http://localhost:8000/api/extras/custom-fields/5b39ba88-e5ab-4be2-89f5-5a016473b53c/notes/" + } +] diff --git a/tests/test_app.py b/tests/test_app.py index 31303dd..36d387e 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -3,6 +3,8 @@ import pynautobot +from .util import Response + host = "http://localhost:8000" def_kwargs = { @@ -10,16 +12,53 @@ } -class AppCustomChoicesTestCase(unittest.TestCase): +class AppCustomFieldsTestCase(unittest.TestCase): @patch( - "pynautobot.core.query.Request.get", - return_value={"Testfield1": {"TF1_1": 1, "TF1_2": 2}, "Testfield2": {"TF2_1": 3, "TF2_2": 4}}, + "requests.sessions.Session.get", + return_value=Response(fixture="extras/custom_fields.json"), ) - def test_custom_choices(self, *_): + def test_custom_fields(self, session_get_mock): api = pynautobot.api(host, **def_kwargs) - choices = api.extras.custom_choices() - self.assertEqual(len(choices), 2) - self.assertEqual(sorted(choices.keys()), ["Testfield1", "Testfield2"]) + cfs = api.extras.custom_fields() + + session_get_mock.assert_called_once() + expect_url = f"{api.base_url}/extras/custom-fields/" + self.assertGreaterEqual(len(session_get_mock.call_args), 2) + url_passed_in_args = expect_url in session_get_mock.call_args[0] + url_passed_in_kwargs = expect_url == session_get_mock.call_args[1].get("url") + self.assertTrue(url_passed_in_args or url_passed_in_kwargs) + + self.assertIsInstance(cfs, list) + self.assertEqual(len(cfs), 2) + for field in cfs: + self.assertIsInstance(field.get("name"), str) + self.assertIsInstance(field.get("content_types"), list) + self.assertIsInstance(field.get("slug"), str) + self.assertIn("type", field) + + +class AppCustomFieldChoicesTestCase(unittest.TestCase): + @patch( + "requests.sessions.Session.get", + return_value=Response(fixture="extras/custom_field_choices.json"), + ) + def test_custom_field_choices(self, session_get_mock): + api = pynautobot.api(host, **def_kwargs) + choices = api.extras.custom_field_choices() + + session_get_mock.assert_called_once() + expect_url = f"{api.base_url}/extras/custom-field-choices/" + self.assertGreaterEqual(len(session_get_mock.call_args), 2) + url_passed_in_args = expect_url in session_get_mock.call_args[0] + url_passed_in_kwargs = expect_url == session_get_mock.call_args[1].get("url") + self.assertTrue(url_passed_in_args or url_passed_in_kwargs) + + self.assertIsInstance(choices, list) + self.assertEqual(len(choices), 3) + for choice in choices: + self.assertIsInstance(choice.get("field"), dict) + self.assertIsInstance(choice.get("value"), str) + self.assertIn(choice.get("value"), ("First option", "Second option", "Third option")) class AppConfigTestCase(unittest.TestCase): @@ -44,7 +83,7 @@ class PluginAppCustomChoicesTestCase(unittest.TestCase): ) def test_custom_choices(self, *_): api = pynautobot.api(host, **def_kwargs) - choices = api.plugins.test_plugin.custom_choices() + choices = api.plugins.test_plugin.custom_fields() self.assertEqual(len(choices), 2) self.assertEqual(sorted(choices.keys()), ["Testfield1", "Testfield2"])