Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into ltm-1.6
Browse files Browse the repository at this point in the history
  • Loading branch information
pszulczewski committed Oct 3, 2023
2 parents 9e7af89 + 2f982c3 commit 1c5fe6d
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 25 deletions.
12 changes: 12 additions & 0 deletions pynautobot/core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
This file has been modified by NetworktoCode, LLC.
"""
from packaging import version
import requests
from requests.adapters import HTTPAdapter
from urllib3 import Retry
Expand Down Expand Up @@ -54,6 +55,8 @@ class Api(object):
:param str token: Your Nautobot token.
:param bool,optional threading: Set to True to use threading in ``.all()``
and ``.filter()`` requests.
:param int,optional max_workers: Set the maximum workers for threading in ``.all()``
and ``.filter()`` requests.
:param str,optional api_version: Set to override the default Nautobot REST API Version
for all requests.
:param int,optional retries: Number of retries, for HTTP codes 429, 500, 502, 503, 504,
Expand All @@ -74,6 +77,7 @@ def __init__(
url,
token=None,
threading=False,
max_workers=4,
api_version=None,
retries=0,
):
Expand All @@ -94,6 +98,7 @@ def __init__(
self.http_session.mount("http://", _adapter)
self.http_session.mount("https://", _adapter)
self.threading = threading
self.max_workers = max_workers
self.api_version = api_version

self.dcim = App(self, "dcim")
Expand All @@ -105,6 +110,13 @@ def __init__(
self.users = App(self, "users")
self.plugins = PluginsApp(self)
self.graphql = GraphQLQuery(self)
self._validate_version()

def _validate_version(self):
"""Validate API version if eq or ge than 2.0 raise an error."""
api_version = self.version
if api_version.replace(".", "").isnumeric() and version.parse(api_version) >= version.parse("2.0"):
raise ValueError("Nautobot version 2 detected, please upgrade pynautobot to version 2.x")

@property
def version(self):
Expand Down
87 changes: 83 additions & 4 deletions pynautobot/core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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()
Expand Down
1 change: 1 addition & 0 deletions pynautobot/core/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def all(self, api_version=None):
token=self.token,
http_session=self.api.http_session,
threading=self.api.threading,
max_workers=self.api.max_workers,
api_version=api_version,
)

Expand Down
6 changes: 5 additions & 1 deletion pynautobot/core/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ class Request(object):
correlate to the filters a given endpoint accepts.
In (e.g. /api/dcim/devices/?name='test') 'name': 'test'
would be in the filters dict.
:param int,optional max_workers: Set the maximum workers for threading in ``.all()``
and ``.filter()`` requests.
"""

def __init__(
Expand All @@ -131,6 +133,7 @@ def __init__(
key=None,
token=None,
threading=False,
max_workers=4,
api_version=None,
):
"""
Expand All @@ -152,6 +155,7 @@ def __init__(
self.http_session = http_session
self.url = self.base if not key else "{}{}/".format(self.base, key)
self.threading = threading
self.max_workers = max_workers
self.api_version = api_version

def get_openapi(self):
Expand Down Expand Up @@ -277,7 +281,7 @@ def _make_call(self, verb="get", url_override=None, add_params=None, data=None):

def concurrent_get(self, ret, page_size, page_offsets):
futures_to_results = []
with cf.ThreadPoolExecutor(max_workers=4) as pool:
with cf.ThreadPoolExecutor(max_workers=self.max_workers) as pool:
for offset in page_offsets:
new_params = {"offset": offset, "limit": page_size}
futures_to_results.append(pool.submit(self._make_call, add_params=new_params))
Expand Down
9 changes: 5 additions & 4 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@

from .util import Response

api = pynautobot.api(
"http://localhost:8000",
token="abc123",
)
with patch("pynautobot.api.version", "1.999"):
api = pynautobot.api(
"http://localhost:8000",
token="abc123",
)

HEADERS = {
"accept": "application/json;",
Expand Down
3 changes: 2 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ def pytest_configure(config):


@pytest.fixture
def pynautobot_api():
def pynautobot_api(monkeypatch):
"""Factory to create pynautobot api instance."""
monkeypatch.setattr("pynautobot.api.version", "1.999")
return Api(url="https://mocknautobot.example.com", token="1234567890abcdefg")


Expand Down
47 changes: 47 additions & 0 deletions tests/fixtures/extras/custom_field_choices.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
60 changes: 60 additions & 0 deletions tests/fixtures/extras/custom_fields.json
Original file line number Diff line number Diff line change
@@ -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/"
}
]
2 changes: 1 addition & 1 deletion tests/integration/test_api_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TestAPIVersioning:
def nb_client_1_3(self, nb_client):
"""Setup a nb_client with API v1.3."""
# Instantiate with a temp url and then replace
nb_api = pynautobot.api("http://localhost", token=nb_client.token, api_version="1.3")
nb_api = pynautobot.api("http://nautobot:8000", token=nb_client.token, api_version="1.3")
nb_api.base_url = nb_client.base_url

return nb_api
Expand Down
Loading

0 comments on commit 1c5fe6d

Please sign in to comment.