def save(self): """Saves changes to an existing object. Takes a diff between the objects current state and its state at init and sends them as a dictionary to Request.patch(). :returns: True if PATCH request was successful. :example: >>> x = nb.dcim.devices.get(name='test1-a3-tor1b') >>> x.serial u'' >>> x.serial = '1234' >>> x.save() True >>> """ if self.id: diff = self._diff() if diff: serialized = self.serialize() req = Request( key=self.id, base=self.endpoint.url, token=self.api.token, http_session=self.api.http_session, ) if req.patch({i: serialized[i] for i in diff}): return True return False
def all(self, api_version=None): """Queries the 'ListView' of a given endpoint. Returns all objects from an endpoint. :arg str,optional api_version: Override default or globally-set Nautobot REST API version for this single request. :Returns: List of :py:class:`.Record` objects. :Examples: >>> nb.dcim.devices.all() [test1-a3-oobsw2, test1-a3-oobsw3, test1-a3-oobsw4] >>> """ api_version = api_version or self.api.api_version req = Request( base="{}/".format(self.url), token=self.token, http_session=self.api.http_session, threading=self.api.threading, api_version=api_version, ) return response_loader(req.get(), self.return_obj, self)
def config(self): """Returns config response from app :Returns: Raw response from Nautobot's config endpoint. :Raises: :py:class:`.RequestError` if called for an invalid endpoint. :Example: >>> pprint.pprint(nb.users.config()) {'tables': {'DeviceTable': {'columns': ['name', 'status', 'tenant', 'device_role', 'site', 'primary_ip', 'tags']}}} """ config = Request( base="{}/{}/config/".format( self.api.base_url, self.name, ), token=self.api.token, http_session=self.api.http_session, ).get() return config
def test_get_count(self): test_obj = Request( http_session=Mock(), base="http://localhost:8001/api/dcim/devices", filters={"q": "abcd"}, ) test_obj.http_session.get.return_value.json.return_value = { "count": 42, "next": "http://localhost:8001/api/dcim/devices?limit=1&offset=1&q=abcd", "previous": False, "results": [], } call( "http://localhost:8001/api/dcim/devices/", params={ "q": "abcd", "limit": 1 }, headers={"accept": "application/json;"}, ) test_obj.http_session.get.ok = True test = test_obj.get_count() self.assertEqual(test, 42) test_obj.http_session.get.assert_called_with( "http://localhost:8001/api/dcim/devices/", params={ "q": "abcd", "limit": 1 }, headers={"accept": "application/json;"}, json=None, )
def create(self, *args, api_version=None, **kwargs): r"""Creates an object on an endpoint. Allows for the creation of new objects on an endpoint. Named arguments are converted to json properties, and a single object is created. Nautobot's bulk creation capabilities can be used by passing a list of dictionaries as the first argument. .. note: Any positional arguments will supercede named ones. :arg list \*args: A list of dictionaries containing the properties of the objects to be created. :arg str \**kwargs: key/value strings representing properties on a json object. :arg str,optional api_version: Override default or globally-set Nautobot REST API version for this single request. :returns: A list or single :py:class:`.Record` object depending on whether a bulk creation was requested. :Examples: Creating an object on the `devices` endpoint you can lookup a device_role's name with: >>> nautobot.dcim.devices.create( ... name='test', ... device_role=1, ... ) >>> Use bulk creation by passing a list of dictionaries: >>> nb.dcim.devices.create([ ... { ... "name": "test1-core3", ... "device_role": 3, ... "site": 1, ... "device_type": 1, ... "status": 1 ... }, ... { ... "name": "test1-core4", ... "device_role": 3, ... "site": 1, ... "device_type": 1, ... "status": 1 ... } ... ]) """ api_version = api_version or self.api.api_version req = Request( base=self.url, token=self.token, http_session=self.api.http_session, api_version=api_version, ).post(args[0] if args else kwargs) return response_loader(req, self.return_obj, self)
def status(self): """Gets the status information from Nautobot. Available in Nautobot 2.10.0 or newer. :Returns: Dictionary as returned by Nautobot. :Raises: :py:class:`.RequestError` if the request is not successful. :Example: >>> pprint.pprint(nb.status()) {'django-version': '3.1.3', 'installed-apps': {'cacheops': '5.0.1', 'debug_toolbar': '3.1.1', 'django_filters': '2.4.0', 'django_prometheus': '2.1.0', 'django_rq': '2.4.0', 'django_tables2': '2.3.3', 'drf_yasg': '1.20.0', 'mptt': '0.11.0', 'rest_framework': '3.12.2', 'taggit': '1.3.0', 'timezone_field': '4.0'}, 'nautobot-version': '1.0.0', 'plugins': {}, 'python-version': '3.7.3', 'rq-workers-running': 1} >>> """ status = Request(base=self.base_url, token=self.token, http_session=self.http_session,).get_status() return status
def test_get_openapi(self): test = Request("http://localhost:8080/api", Mock()) test.get_openapi() test.http_session.get.assert_called_with( "http://localhost:8080/api/docs/?format=openapi", headers={"Content-Type": "application/json;"}, )
def choices(self, api_version=None): """Returns all choices from the endpoint. The returned dict is also saved in the endpoint object (in ``_choices`` attribute) so that later calls will return the same data without recurring requests to Nautobot. When using ``.choices()`` in long-running applications, consider restarting them whenever Nautobot is upgraded, to prevent using stale choices data. :arg str,optional api_version: Override default or globally-set Nautobot REST API version for this single request. :Returns: Dict containing the available choices. :Example (from Nautobot 2.8.x): >>> from pprint import pprint >>> pprint(nb.ipam.ip_addresses.choices()) {'role': [{'display_name': 'Loopback', 'value': 'loopback'}, {'display_name': 'Secondary', 'value': 'secondary'}, {'display_name': 'Anycast', 'value': 'anycast'}, {'display_name': 'VIP', 'value': 'vip'}, {'display_name': 'VRRP', 'value': 'vrrp'}, {'display_name': 'HSRP', 'value': 'hsrp'}, {'display_name': 'GLBP', 'value': 'glbp'}, {'display_name': 'CARP', 'value': 'carp'}], 'status': [{'display_name': 'Active', 'value': 'active'}, {'display_name': 'Reserved', 'value': 'reserved'}, {'display_name': 'Deprecated', 'value': 'deprecated'}, {'display_name': 'DHCP', 'value': 'dhcp'}]} >>> """ if self._choices: return self._choices api_version = api_version or self.api.api_version req = Request( base=self.url, token=self.api.token, http_session=self.api.http_session, api_version=api_version, ).options() try: post_data = req["actions"]["POST"] except KeyError: raise ValueError("Unexpected format in the OPTIONS response at {}".format(self.url)) self._choices = {} for prop in post_data: if "choices" in post_data[prop]: self._choices[prop] = post_data[prop]["choices"] return self._choices
def count(self, *args, **kwargs): r"""Returns the count of objects in a query. Takes named arguments that match the usable filters on a given endpoint. If an argument is passed then it's used as a freeform search argument if the endpoint supports it. If no arguments are passed the count for all objects on an endpoint are returned. :arg str,optional \*args: Freeform search string that's accepted on given endpoint. :arg str,optional \**kwargs: Any search argument the endpoint accepts can be added as a keyword arg. :Returns: Integer with count of objects returns by query. :Examples: To return a count of objects matching a named argument filter. >>> nb.dcim.devices.count(site='tst1') 5827 >>> To return a count of objects on an entire endpoint. >>> nb.dcim.devices.count() 87382 >>> """ if args: kwargs.update({"q": args[0]}) if any(i in RESERVED_KWARGS for i in kwargs): raise ValueError( "A reserved {} kwarg was passed. Please remove it " "try again.".format(RESERVED_KWARGS)) ret = Request( filters=kwargs, base=self.url, token=self.token, http_session=self.api.http_session, ) return ret.get_count()
def delete(self): """Deletes an existing object. :returns: True if DELETE operation was successful. :example: >>> x = nb.dcim.devices.get(name='test1-a3-tor1b') >>> x.delete() True >>> """ req = Request( key=self.id, base=self.endpoint.url, token=self.api.token, http_session=self.api.http_session, ) return True if req.delete() else False
def list(self, **kwargs): r"""The view operation for a detail endpoint Returns the response from Nautobot for a detail endpoint. Args: **kwargs: key/value pairs that get converted into url parameters when passed to the endpoint. E.g. ``.list(method='get_facts')`` would be converted to ``.../?method=get_facts``. :returns: A dictionary or list of dictionaries retrieved from Nautobot. """ req = Request(**self.request_kwargs).get(add_params=kwargs) if self.custom_return: return response_loader(req, self.custom_return, self.parent_obj.endpoint) return req
def openapi(self): """Returns the OpenAPI spec. Quick helper function to pull down the entire OpenAPI spec. :Returns: dict :Example: >>> import pynautobot >>> nb = pynautobot.api( ... 'http://localhost:8000', ... token='d6f4e314a5b5fefd164995169f28ae32d987704f' ... ) >>> nb.openapi() {...} >>> """ return Request(base=self.base_url, http_session=self.http_session,).get_openapi()
def create(self, data=None): """The write operation for a detail endpoint. Creates objects on a detail endpoint in Nautobot. :arg dict/list,optional data: A dictionary containing the key/value pair of the items you're creating on the parent object. Defaults to empty dict which will create a single item with default values. :returns: A dictionary or list of dictionaries its created in Nautobot. """ data = data or {} req = Request(**self.request_kwargs).post(data) if self.custom_return: return response_loader(req, self.custom_return, self.parent_obj.endpoint) return req
def list(self, api_version=None, **kwargs): r"""The view operation for a detail endpoint Returns the response from Nautobot for a detail endpoint. Args: :arg str,optional api_version: Override default or globally set Nautobot REST API version for this single request. **kwargs: key/value pairs that get converted into url parameters when passed to the endpoint. E.g. ``.list(method='get_facts')`` would be converted to ``.../?method=get_facts``. :returns: A dictionary or list of dictionaries retrieved from Nautobot. """ api_version = api_version or self.parent_obj.api.api_version req = Request(api_version=api_version, **self.request_kwargs).get(add_params=kwargs) if self.custom_return: return response_loader(req, self.custom_return, self.parent_obj.endpoint) return req
def custom_choices(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_choices() {'Testfield1': {'Testvalue2': 2, 'Testvalue1': 1}, 'Testfield2': {'Othervalue2': 4, 'Othervalue1': 3}} """ custom_fields = Request( base="{}/{}/custom-fields/".format( self.api.base_url, self.name, ), token=self.api.token, http_session=self.api.http_session, ).get() return custom_fields
def version(self): """Gets the API version of Nautobot. Can be used to check the Nautobot API version if there are version-dependent features or syntaxes in the API. :Returns: Version number as a string. :Example: >>> import pynautobot >>> nb = pynautobot.api( ... 'http://localhost:8000', ... token='d6f4e314a5b5fefd164995169f28ae32d987704f' ... ) >>> nb.version '1.0' >>> """ version = Request(base=self.base_url, http_session=self.http_session,).get_version() return version
def full_details(self): """Queries the hyperlinked endpoint if 'url' is defined. This method will populate the attributes from the detail endpoint when it's called. Sets the class-level `has_details` attribute when it's called to prevent being called more than once. :returns: True """ if self.url: req = Request( base=self.url, token=self.api.token, http_session=self.api.http_session, ) self._parse_values(req.get()) self.has_details = True return True return False
def choices(self): """Returns _choices response from App .. note:: This method is deprecated and only works with Nautobot version 2.7.x or older. The ``choices()`` method in :py:class:`.Endpoint` is compatible with all Nautobot versions. :Returns: Raw response from Nautobot's _choices endpoint. """ if self._choices: return self._choices self._choices = Request( base="{}/{}/_choices/".format(self.api.base_url, self.name), token=self.api.token, http_session=self.api.http_session, ).get() return self._choices
def installed_plugins(self): """Returns raw response with installed plugins :returns: Raw response Nautobot's installed plugins. :Example: >>> nb.plugins.installed_plugins() [{ 'name': 'test_plugin', 'package': 'test_plugin', 'author': 'Dmitry', 'description': 'Nautobot test plugin', 'verison': '0.10' }] """ installed_plugins = Request( base="{}/plugins/installed-plugins".format(self.api.base_url, ), token=self.api.token, http_session=self.api.http_session, ).get() return installed_plugins
def all(self): """Queries the 'ListView' of a given endpoint. Returns all objects from an endpoint. :Returns: List of :py:class:`.Record` objects. :Examples: >>> nb.dcim.devices.all() [test1-a3-oobsw2, test1-a3-oobsw3, test1-a3-oobsw4] >>> """ req = Request( base="{}/".format(self.url), token=self.token, http_session=self.api.http_session, threading=self.api.threading, ) return response_loader(req.get(), self.return_obj, self)
def trace(self): req = Request( key=str(self.id) + "/trace", base=self.endpoint.url, token=self.api.token, http_session=self.api.http_session, ) uri_to_obj_class_map = { "dcim/cables": Cables, "dcim/front-ports": FrontPorts, "dcim/interfaces": Interfaces, "dcim/rear-ports": RearPorts, } ret = [] for (termination_a_data, cable_data, termination_b_data) in req.get(): this_hop_ret = [] for hop_item_data in (termination_a_data, cable_data, termination_b_data): # if not fully terminated then some items will be None if not hop_item_data: this_hop_ret.append(hop_item_data) continue # TODO: Move this to a more general function. app_endpoint = "/".join( urlparse(hop_item_data["url"] [len(self.api.base_url):]).path.split("/")[1:3]) return_obj_class = uri_to_obj_class_map.get( app_endpoint, Record, ) this_hop_ret.append( return_obj_class(hop_item_data, self.endpoint.api, self.endpoint)) ret.append(this_hop_ret) return ret
def create(self, data=None, api_version=None): """The write operation for a detail endpoint. Creates objects on a detail endpoint in Nautobot. :arg dict/list,optional data: A dictionary containing the key/value pair of the items you're creating on the parent object. Defaults to empty dict which will create a single item with default values. :args str,optional api_version: Override default or globally set Nautobot REST API version for this single request. :returns: A dictionary or list of dictionaries its created in Nautobot. """ data = data or {} api_version = api_version or self.parent_obj.api.api_version req = Request(api_version=api_version, **self.request_kwargs).post(data) if self.custom_return: return response_loader(req, self.custom_return, self.parent_obj.endpoint) return req
def get(self, *args, **kwargs): r"""Queries the DetailsView of a given endpoint. :arg int,optional key: id for the item to be retrieved. :arg str,optional \**kwargs: Accepts the same keyword args as filter(). Any search argument the endpoint accepts can be added as a keyword arg. :returns: A single :py:class:`.Record` object or None :raises ValueError: if kwarg search return more than one value. :Examples: Referencing with a kwarg that only returns one value. >>> nb.dcim.devices.get(name='test1-a3-tor1b') test1-a3-tor1b >>> Referencing with an id. >>> nb.dcim.devices.get(1) test1-edge1 >>> """ try: key = args[0] except IndexError: key = None if not key: filter_lookup = self.filter(**kwargs) if filter_lookup: if len(filter_lookup) > 1: raise ValueError( "get() returned more than one result. " "Check that the kwarg(s) passed are valid for this " "endpoint or use filter() or all() instead.") else: return filter_lookup[0] return None req = Request( key=key, base=self.url, token=self.token, http_session=self.api.http_session, ) try: resp = req.get() except RequestError as e: if e.req.status_code == 404: return None else: raise e return response_loader(resp, self.return_obj, self)
def filter(self, *args, **kwargs): r"""Queries the 'ListView' of a given endpoint. Takes named arguments that match the usable filters on a given endpoint. If an argument is passed then it's used as a freeform search argument if the endpoint supports it. :arg str,optional \*args: Freeform search string that's accepted on given endpoint. :arg str,optional \**kwargs: Any search argument the endpoint accepts can be added as a keyword arg. :Returns: A list of :py:class:`.Record` objects. :Examples: To return a list of objects matching a named argument filter. >>> nb.dcim.devices.filter(role='leaf-switch') [test1-a3-tor1b, test1-a3-tor1c, test1-a3-tor1d, test1-a3-tor2a] >>> Using a freeform query along with a named argument. >>> nb.dcim.devices.filter('a3', role='leaf-switch') [test1-a3-tor1b, test1-a3-tor1c, test1-a3-tor1d, test1-a3-tor2a] >>> Chaining multiple named arguments. >>> nb.dcim.devices.filter(role='leaf-switch', status=True) [test1-leaf2] >>> Passing a list as a named argument adds multiple filters of the same value. >>> nb.dcim.devices.filter(role=['leaf-switch', 'spine-switch']) [test1-a3-spine1, test1-a3-spine2, test1-a3-leaf1] >>> """ if args: kwargs.update({"q": args[0]}) if not kwargs: raise ValueError( "filter must be passed kwargs. Perhaps use all() instead.") if any(i in RESERVED_KWARGS for i in kwargs): raise ValueError( "A reserved {} kwarg was passed. Please remove it " "try again.".format(RESERVED_KWARGS)) req = Request( filters=kwargs, base=self.url, token=self.token, http_session=self.api.http_session, threading=self.api.threading, ) return response_loader(req.get(), self.return_obj, self)