New Upstream Release - python-pynetbox

Ready changes

Summary

Merged new upstream version: 7.0.1 (was: 7.0.0).

Resulting package

Built on 2023-07-09T05:05 (took 7m41s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-pynetbox

Lintian Result

Diff

diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index 6c79a08..0000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,16 +0,0 @@
-Metadata-Version: 2.1
-Name: pynetbox
-Version: 7.0.0
-Summary: NetBox API client library
-Home-page: https://github.com/netbox-community/netbox
-Author: Zach Moody
-Author-email: zmoody@do.co
-License: Apache2
-Keywords: netbox
-Classifier: Intended Audience :: Developers
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-License-File: LICENSE
diff --git a/README.md b/README.md
index 522550c..a1dbecf 100644
--- a/README.md
+++ b/README.md
@@ -20,12 +20,11 @@ To begin, import pynetbox and instantiate the API.
 import pynetbox
 nb = pynetbox.api(
     'http://localhost:8000',
-    private_key_file='/path/to/private-key.pem',
     token='d6f4e314a5b5fefd164995169f28ae32d987704f'
 )
 ```
 
-The first argument the .api() method takes is the NetBox URL. There are a handful of named arguments you can provide, but in most cases none are required to simply pull data. In order to write, the `token` argument should to be provided. To decrypt information from the `secrets` endpoint either the `private_key_file` or `private_key` argument needs to be provided.
+The first argument the .api() method takes is the NetBox URL. There are a handful of named arguments you can provide, but in most cases none are required to simply pull data. In order to write, the `token` argument should to be provided.
 
 
 ## Queries
@@ -43,6 +42,11 @@ test1-leaf3
 >>>
 ```
 
+Note that the all() and filter() methods are generators and return an object that can be iterated over only once.  If you are going to be iterating over it repeatedly you need to either call the all() method again, or encapsulate the results in a `list` object like this:
+```
+>>> devices = list(nb.dcim.devices.all())
+```
+
 ### Threading
 
 pynetbox supports multithreaded calls for `.filter()` and `.all()` queries. It is **highly recommended** you have `MAX_PAGE_SIZE` in your Netbox install set to anything *except* `0` or `None`. The default value of `1000` is usually a good value to use. To enable threading, add `threading=True` parameter to the `.api`:
diff --git a/debian/changelog b/debian/changelog
index 0720fd1..ca8a41e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-pynetbox (7.0.1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 09 Jul 2023 04:58:43 -0000
+
 python-pynetbox (7.0.0-1) unstable; urgency=medium
 
   * New upstream version 7.0.0
diff --git a/docs/advanced.rst b/docs/advanced.rst
index 991eb17..577e31d 100644
--- a/docs/advanced.rst
+++ b/docs/advanced.rst
@@ -16,7 +16,6 @@ To set a custom header on all requests. These headers are automatically merged w
 >>> session.headers = {"mycustomheader": "test"}
 >>> nb = pynetbox.api(
 ...     'http://localhost:8000',
-...     private_key_file='/path/to/private-key.pem',
 ...     token='d6f4e314a5b5fefd164995169f28ae32d987704f'
 ... )
 >>> nb.http_session = session
@@ -35,7 +34,6 @@ To disable SSL verification. See `the docs <https://requests.readthedocs.io/en/s
 >>> session.verify = False
 >>> nb = pynetbox.api(
 ...     'http://localhost:8000',
-...     private_key_file='/path/to/private-key.pem',
 ...     token='d6f4e314a5b5fefd164995169f28ae32d987704f'
 ... )
 >>> nb.http_session = session
@@ -68,7 +66,6 @@ Setting timeouts requires the use of Adapters.
 
     nb = pynetbox.api(
         'http://localhost:8000',
-        private_key_file='/path/to/private-key.pem',
         token='d6f4e314a5b5fefd164995169f28ae32d987704f'
     )
     nb.http_session = session
diff --git a/pynetbox.egg-info/PKG-INFO b/pynetbox.egg-info/PKG-INFO
deleted file mode 100644
index 6c79a08..0000000
--- a/pynetbox.egg-info/PKG-INFO
+++ /dev/null
@@ -1,16 +0,0 @@
-Metadata-Version: 2.1
-Name: pynetbox
-Version: 7.0.0
-Summary: NetBox API client library
-Home-page: https://github.com/netbox-community/netbox
-Author: Zach Moody
-Author-email: zmoody@do.co
-License: Apache2
-Keywords: netbox
-Classifier: Intended Audience :: Developers
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-License-File: LICENSE
diff --git a/pynetbox.egg-info/SOURCES.txt b/pynetbox.egg-info/SOURCES.txt
deleted file mode 100644
index 1014168..0000000
--- a/pynetbox.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,174 +0,0 @@
-.gitignore
-CHANGELOG.md
-LICENSE
-README.md
-requirements-dev.txt
-requirements.txt
-setup.py
-.github/workflows/publish.yml
-.github/workflows/py3.yml
-docs/IPAM.rst
-docs/Makefile
-docs/advanced.rst
-docs/conf.py
-docs/endpoint.rst
-docs/index.rst
-docs/request.rst
-docs/response.rst
-pynetbox/__init__.py
-pynetbox.egg-info/PKG-INFO
-pynetbox.egg-info/SOURCES.txt
-pynetbox.egg-info/dependency_links.txt
-pynetbox.egg-info/not-zip-safe
-pynetbox.egg-info/requires.txt
-pynetbox.egg-info/top_level.txt
-pynetbox/core/__init__.py
-pynetbox/core/api.py
-pynetbox/core/app.py
-pynetbox/core/endpoint.py
-pynetbox/core/query.py
-pynetbox/core/response.py
-pynetbox/core/util.py
-pynetbox/models/__init__.py
-pynetbox/models/circuits.py
-pynetbox/models/dcim.py
-pynetbox/models/extras.py
-pynetbox/models/ipam.py
-pynetbox/models/mapper.py
-pynetbox/models/users.py
-pynetbox/models/virtualization.py
-pynetbox/models/wireless.py
-tests/__init__.py
-tests/conftest.py
-tests/test_api.py
-tests/test_app.py
-tests/test_circuits.py
-tests/test_tenancy.py
-tests/test_users.py
-tests/test_virtualization.py
-tests/test_wireless.py
-tests/util.py
-tests/fixtures/api/get_session_key.json
-tests/fixtures/api/token_provision.json
-tests/fixtures/circuits/circuit.json
-tests/fixtures/circuits/circuit_termination.json
-tests/fixtures/circuits/circuit_terminations.json
-tests/fixtures/circuits/circuit_type.json
-tests/fixtures/circuits/circuit_types.json
-tests/fixtures/circuits/circuits.json
-tests/fixtures/circuits/provider.json
-tests/fixtures/circuits/providers.json
-tests/fixtures/dcim/cable.json
-tests/fixtures/dcim/cables.json
-tests/fixtures/dcim/choices.json
-tests/fixtures/dcim/console_port.json
-tests/fixtures/dcim/console_port_template.json
-tests/fixtures/dcim/console_port_templates.json
-tests/fixtures/dcim/console_ports.json
-tests/fixtures/dcim/console_server_port.json
-tests/fixtures/dcim/console_server_port_template.json
-tests/fixtures/dcim/console_server_port_templates.json
-tests/fixtures/dcim/console_server_ports.json
-tests/fixtures/dcim/device.json
-tests/fixtures/dcim/device_bay.json
-tests/fixtures/dcim/device_bay_template.json
-tests/fixtures/dcim/device_bay_templates.json
-tests/fixtures/dcim/device_bays.json
-tests/fixtures/dcim/device_bulk_create.json
-tests/fixtures/dcim/device_role.json
-tests/fixtures/dcim/device_roles.json
-tests/fixtures/dcim/device_type.json
-tests/fixtures/dcim/device_types.json
-tests/fixtures/dcim/devices.json
-tests/fixtures/dcim/interface.json
-tests/fixtures/dcim/interface_connection.json
-tests/fixtures/dcim/interface_connections.json
-tests/fixtures/dcim/interface_template.json
-tests/fixtures/dcim/interface_templates.json
-tests/fixtures/dcim/interface_trace.json
-tests/fixtures/dcim/interfaces.json
-tests/fixtures/dcim/interfaces_1.json
-tests/fixtures/dcim/interfaces_2.json
-tests/fixtures/dcim/inventory_item.json
-tests/fixtures/dcim/inventory_items.json
-tests/fixtures/dcim/manufacturer.json
-tests/fixtures/dcim/manufacturers.json
-tests/fixtures/dcim/napalm.json
-tests/fixtures/dcim/platform.json
-tests/fixtures/dcim/platforms.json
-tests/fixtures/dcim/power_outlet.json
-tests/fixtures/dcim/power_outlet_template.json
-tests/fixtures/dcim/power_outlet_templates.json
-tests/fixtures/dcim/power_outlets.json
-tests/fixtures/dcim/power_port.json
-tests/fixtures/dcim/power_port_template.json
-tests/fixtures/dcim/power_port_templates.json
-tests/fixtures/dcim/power_ports.json
-tests/fixtures/dcim/rack.json
-tests/fixtures/dcim/rack_group.json
-tests/fixtures/dcim/rack_groups.json
-tests/fixtures/dcim/rack_reservation.json
-tests/fixtures/dcim/rack_reservations.json
-tests/fixtures/dcim/rack_role.json
-tests/fixtures/dcim/rack_roles.json
-tests/fixtures/dcim/rack_u.json
-tests/fixtures/dcim/racks.json
-tests/fixtures/dcim/region.json
-tests/fixtures/dcim/regions.json
-tests/fixtures/dcim/site.json
-tests/fixtures/dcim/sites.json
-tests/fixtures/dcim/virtual_chassis_device.json
-tests/fixtures/dcim/virtual_chassis_devices.json
-tests/fixtures/ipam/aggregate.json
-tests/fixtures/ipam/aggregates.json
-tests/fixtures/ipam/available-ips-post.json
-tests/fixtures/ipam/available-ips.json
-tests/fixtures/ipam/available-prefixes-post.json
-tests/fixtures/ipam/available-prefixes.json
-tests/fixtures/ipam/ip_address.json
-tests/fixtures/ipam/ip_addresses.json
-tests/fixtures/ipam/prefix.json
-tests/fixtures/ipam/prefixes.json
-tests/fixtures/ipam/rir.json
-tests/fixtures/ipam/rirs.json
-tests/fixtures/ipam/role.json
-tests/fixtures/ipam/roles.json
-tests/fixtures/ipam/vlan.json
-tests/fixtures/ipam/vlan_group.json
-tests/fixtures/ipam/vlan_groups.json
-tests/fixtures/ipam/vlans.json
-tests/fixtures/ipam/vrf.json
-tests/fixtures/ipam/vrfs.json
-tests/fixtures/tenancy/tenant.json
-tests/fixtures/tenancy/tenant_group.json
-tests/fixtures/tenancy/tenant_groups.json
-tests/fixtures/tenancy/tenants.json
-tests/fixtures/users/group.json
-tests/fixtures/users/groups.json
-tests/fixtures/users/permission.json
-tests/fixtures/users/permissions.json
-tests/fixtures/users/unknown_model.json
-tests/fixtures/users/user.json
-tests/fixtures/users/users.json
-tests/fixtures/virtualization/cluster.json
-tests/fixtures/virtualization/cluster_group.json
-tests/fixtures/virtualization/cluster_groups.json
-tests/fixtures/virtualization/cluster_type.json
-tests/fixtures/virtualization/cluster_types.json
-tests/fixtures/virtualization/clusters.json
-tests/fixtures/virtualization/interface.json
-tests/fixtures/virtualization/interfaces.json
-tests/fixtures/virtualization/virtual_machine.json
-tests/fixtures/virtualization/virtual_machines.json
-tests/fixtures/wireless/wireless_lan.json
-tests/fixtures/wireless/wireless_lans.json
-tests/integration/conftest.py
-tests/integration/test_dcim.py
-tests/integration/test_ipam.py
-tests/unit/__init__.py
-tests/unit/test_detailendpoint.py
-tests/unit/test_endpoint.py
-tests/unit/test_extras.py
-tests/unit/test_query.py
-tests/unit/test_request.py
-tests/unit/test_response.py
\ No newline at end of file
diff --git a/pynetbox.egg-info/dependency_links.txt b/pynetbox.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/pynetbox.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/pynetbox.egg-info/not-zip-safe b/pynetbox.egg-info/not-zip-safe
deleted file mode 100644
index 8b13789..0000000
--- a/pynetbox.egg-info/not-zip-safe
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/pynetbox.egg-info/requires.txt b/pynetbox.egg-info/requires.txt
deleted file mode 100644
index 9abb9c5..0000000
--- a/pynetbox.egg-info/requires.txt
+++ /dev/null
@@ -1 +0,0 @@
-requests<3.0,>=2.20.0
diff --git a/pynetbox.egg-info/top_level.txt b/pynetbox.egg-info/top_level.txt
deleted file mode 100644
index a7297f0..0000000
--- a/pynetbox.egg-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-pynetbox
diff --git a/pynetbox/core/api.py b/pynetbox/core/api.py
index 2148100..3b9b79d 100644
--- a/pynetbox/core/api.py
+++ b/pynetbox/core/api.py
@@ -30,12 +30,11 @@ class Api:
         * dcim
         * ipam
         * circuits
-        * secrets (on NetBox 2.11 and older)
         * tenancy
         * extras
         * virtualization
-        * users (since NetBox 2.9)
-        * wireless (since NetBox 3.1)
+        * users
+        * wireless
 
     Calling any of these attributes will return
     :py:class:`.App` which exposes endpoints as attributes.
@@ -50,13 +49,8 @@ class Api:
     :param str url: The base URL to the instance of NetBox you
         wish to connect to.
     :param str token: Your NetBox token.
-    :param str,optional private_key_file: The path to your private
-        key file. (Usable only on NetBox 2.11 and older)
-    :param str,optional private_key: Your private key. (Usable only on NetBox 2.11 and older)
     :param bool,optional threading: Set to True to use threading in ``.all()``
         and ``.filter()`` requests.
-    :raises ValueError: If *private_key* and *private_key_file* are both
-        specified.
     :raises AttributeError: If app doesn't exist.
     :Examples:
 
@@ -73,32 +67,16 @@ class Api:
         self,
         url,
         token=None,
-        private_key=None,
-        private_key_file=None,
         threading=False,
     ):
-        if private_key and private_key_file:
-            raise ValueError(
-                '"private_key" and "private_key_file" cannot be used together.'
-            )
         base_url = "{}/api".format(url if url[-1] != "/" else url[:-1])
         self.token = token
-        self.private_key = private_key
-        self.private_key_file = private_key_file
         self.base_url = base_url
-        self.session_key = None
         self.http_session = requests.Session()
         self.threading = threading
-
-        if self.private_key_file:
-            with open(self.private_key_file, "r") as kf:
-                private_key = kf.read()
-                self.private_key = private_key
-
         self.dcim = App(self, "dcim")
         self.ipam = App(self, "ipam")
         self.circuits = App(self, "circuits")
-        self.secrets = App(self, "secrets")
         self.tenancy = App(self, "tenancy")
         self.extras = App(self, "extras")
         self.virtualization = App(self, "virtualization")
@@ -156,8 +134,6 @@ class Api:
     def status(self):
         """Gets the status information from NetBox.
 
-        Available in NetBox 2.10.0 or newer.
-
         :Returns: Dictionary as returned by NetBox.
         :Raises: :py:class:`.RequestError` if the request is not successful.
         :Example:
@@ -192,8 +168,6 @@ class Api:
         """Creates an API token using a valid NetBox username and password.
         Saves the created token automatically in the API object.
 
-        Requires NetBox 3.0.0 or newer.
-
         :Returns: The token as a ``Record`` object.
         :Raises: :py:class:`.RequestError` if the request is not successful.
 
diff --git a/pynetbox/core/app.py b/pynetbox/core/app.py
index 0db9f76..677dd45 100644
--- a/pynetbox/core/app.py
+++ b/pynetbox/core/app.py
@@ -39,7 +39,6 @@ class App:
     def __init__(self, api, name):
         self.api = api
         self.name = name
-        self._choices = None
         self._setmodel()
 
     models = {
@@ -56,79 +55,15 @@ class App:
         self.model = App.models[self.name] if self.name in App.models else None
 
     def __getstate__(self):
-        return {"api": self.api, "name": self.name, "_choices": self._choices}
+        return {"api": self.api, "name": self.name}
 
     def __setstate__(self, d):
         self.__dict__.update(d)
         self._setmodel()
 
     def __getattr__(self, name):
-        if name == "secrets":
-            self._set_session_key()
         return Endpoint(self.api, self, name, model=self.model)
 
-    def _set_session_key(self):
-        if getattr(self.api, "session_key"):
-            return
-        if self.api.token and self.api.private_key:
-            self.api.session_key = Request(
-                base=self.api.base_url,
-                token=self.api.token,
-                private_key=self.api.private_key,
-                http_session=self.api.http_session,
-            ).get_session_key()
-
-    def choices(self):
-        """Returns _choices response from App
-
-        .. note::
-
-            This method is deprecated and only works with NetBox version 2.7.x
-            or older. The ``choices()`` method in :py:class:`.Endpoint` is
-            compatible with all NetBox versions.
-
-        :Returns: Raw response from NetBox'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,
-            private_key=self.api.private_key,
-            http_session=self.api.http_session,
-        ).get()
-
-        return self._choices
-
-    def custom_choices(self):
-        """Returns _custom_field_choices response from app
-
-        .. note::
-
-            This method only works with NetBox version 2.9.x or older. NetBox
-            2.10.0 introduced the ``/extras/custom-fields/`` endpoint that can
-            be used f.ex. like ``nb.extras.custom_fields.all()``.
-
-        :Returns: Raw response from NetBox's _custom_field_choices 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_field_choices = Request(
-            base="{}/{}/_custom_field_choices/".format(
-                self.api.base_url,
-                self.name,
-            ),
-            token=self.api.token,
-            private_key=self.api.private_key,
-            http_session=self.api.http_session,
-        ).get()
-        return custom_field_choices
-
     def config(self):
         """Returns config response from app
 
@@ -151,7 +86,6 @@ class App:
                 self.name,
             ),
             token=self.api.token,
-            private_key=self.api.private_key,
             http_session=self.api.http_session,
         ).get()
         return config
@@ -198,7 +132,6 @@ class PluginsApp:
                 self.api.base_url,
             ),
             token=self.api.token,
-            private_key=self.api.private_key,
             http_session=self.api.http_session,
         ).get()
         return installed_plugins
diff --git a/pynetbox/core/endpoint.py b/pynetbox/core/endpoint.py
index 7938c72..6ba6d01 100644
--- a/pynetbox/core/endpoint.py
+++ b/pynetbox/core/endpoint.py
@@ -46,7 +46,6 @@ class Endpoint:
         self.api = api
         self.base_url = api.base_url
         self.token = api.token
-        self.session_key = api.session_key
         self.url = "{base_url}/{app}/{endpoint}".format(
             base_url=self.base_url,
             app=app.name,
@@ -79,14 +78,16 @@ class Endpoint:
         Returns all objects from an endpoint.
 
         :arg int,optional limit: Overrides the max page size on
-            paginated returns.
+            paginated returns.  This defines the number of records that will
+            be returned with each query to the Netbox server.  The queries
+            will be made as you iterate through the result set.
         :arg int,optional offset: Overrides the offset on paginated returns.
 
         :Returns: A :py:class:`.RecordSet` object.
 
         :Examples:
 
-        >>> devices = nb.dcim.devices.all()
+        >>> devices = list(nb.dcim.devices.all())
         >>> for device in devices:
         ...     print(device.name)
         ...
@@ -94,13 +95,20 @@ class Endpoint:
         test1-leaf2
         test1-leaf3
         >>>
+
+        If you want to iterate over the results multiple times then
+        encapsulate them in a list like this:
+        >>> devices = list(nb.dcim.devices.all())
+
+        This will cause the entire result set
+        to be fetched from the server.
+
         """
         if limit == 0 and offset is not None:
             raise ValueError("offset requires a positive limit value")
         req = Request(
             base="{}/".format(self.url),
             token=self.token,
-            session_key=self.session_key,
             http_session=self.api.http_session,
             threading=self.api.threading,
             limit=limit,
@@ -136,6 +144,11 @@ class Endpoint:
         >>> nb.dcim.devices.get(1)
         test1-edge1
         >>>
+
+        Using multiple named arguments. For example, retriving the location when the location name is not unique and used in multiple sites.
+
+        >>> nb.locations.get(site='site-1', name='Row 1')
+        Row 1
         """
 
         try:
@@ -162,7 +175,6 @@ class Endpoint:
             key=key,
             base=self.url,
             token=self.token,
-            session_key=self.session_key,
             http_session=self.api.http_session,
         )
         try:
@@ -185,7 +197,9 @@ class Endpoint:
         :arg str,optional \**kwargs: Any search argument the
             endpoint accepts can be added as a keyword arg.
         :arg int,optional limit: Overrides the max page size on
-            paginated returns.
+            paginated returns.  This defines the number of records that will
+            be returned with each query to the Netbox server.  The queries
+            will be made as you iterate through the result set.
         :arg int,optional offset: Overrides the offset on paginated returns.
 
         :Returns: A :py:class:`.RecordSet` object.
@@ -203,6 +217,23 @@ class Endpoint:
         test1-leaf3
         >>>
 
+        >>> devices = nb.dcim.devices.filter(site='site-1')
+        >>> for device in devices:
+        ...     print(device.name)
+        ...
+        test1-a2-leaf1
+        test2-a2-leaf2
+        >>>
+
+        If we want to filter by site, location, tenant, or fields which have a display name and a slug, the slug has to be used for filtering.
+
+        .. note::
+
+          If a keyword argument is incorrect a `TypeError` will not be returned by pynetbox.
+          Instead, all records filtered up to the last correct keyword argument. For example, if we used `site="Site 1"` instead of `site=site-1` when using filter on
+          the devices endpoint, then pynetbox will return **all** devices across all sites instead of devices at Site 1.
+
+
         Using a freeform query along with a named argument.
 
         >>> devices = nb.dcim.devices.filter('a3', role='leaf-switch')
@@ -213,6 +244,7 @@ class Endpoint:
         test1-a3-leaf2
         >>>
 
+
         Chaining multiple named arguments.
 
         >>> devices = nb.dcim.devices.filter(role='leaf-switch', status=True)
@@ -233,6 +265,12 @@ class Endpoint:
         test1-a3-spine2
         test1-a3-leaf1
         >>>
+
+        To have the ability to iterate over the results multiple times then
+        encapsulate them in a list.  This will cause the entire result set
+        to be fetched from the server.
+
+        >>> devices = list(nb.dcim.devices.filter(role='leaf-switch'))
         """
 
         if args:
@@ -251,7 +289,6 @@ class Endpoint:
             filters=kwargs,
             base=self.url,
             token=self.token,
-            session_key=self.session_key,
             http_session=self.api.http_session,
             threading=self.api.threading,
             limit=limit,
@@ -290,6 +327,18 @@ class Endpoint:
         ... )
         >>>
 
+        Creating an object on the 'devices' endpoint using `.get()` to get ids.
+
+        >>> device = netbox.dcim.devices.create(
+        ...     name = 'test1',
+        ...     site = netbox.dcim.devices.get(name='site1').id,
+        ...     location = netbox.dcim.locations.get(name='Row 1').id,
+        ...     rack = netbox.dcim.racks.get(facility_id=1).id,
+        ...     device_type = netbox.dcim.device_types.get(slug='server-type-1').id,
+        ...     status='active',
+        ...     )
+        >>>
+
         Use bulk creation by passing a list of dictionaries:
 
         >>> nb.dcim.devices.create([
@@ -308,12 +357,60 @@ class Endpoint:
         ...         "status": 1
         ...     }
         ... ])
+
+        Create a new device type.
+
+        >>> device_type = netbox.dcim.devices.create(
+        ...     manufacturer = netbox.dcim.manufacturers.get(name='manufacturer-name').id,
+        ...     model = 'device-type-name',
+        ...     slug = 'device-type-slug',
+        ...     subdevice_role = 'child or parent', #optional field - requred if creating a device type to be used by child devices
+        ...     u_height = unit_height, #can only equal 0 if the device type is for a child device - requires subdevice_role='child' if that is the case
+        ...     custom_fields = {'cf_1' : 'custom data 1'}
+        ...     )
+
+        Create a device bay and child device.
+
+        >>> device_bay = netbox.dcim.device_bays.create(
+        ...     device = netbox.dcim.devices.get(name='parent device').id, # device the device bay is located
+        ...     name = 'Bay 1'
+        ...     )
+        >>> child_device = netbox.dcim.devices.create(
+        ...     name = 'child device',
+        ...     site = netbox.dcim.devices.get(name='test-site').id,
+        ...     location = netbox.dcim.locations.get(name='row-1').id,
+        ...     tenant = netbox.tenancy.tenants.get(name='tenant-1').id,
+        ...     manufactuer = netbox.dcim.manufacturers.get(name='test-m').id,
+        ...     rack = netbox.dcim.racks.get(name='Test Rack').id,
+        ...     device_type = netbox.dcim.device.types.get(slug='test-server').id, #easier to get device_type id by search by its slug rather than by its name
+        ...     )
+        >>> get_device_bay = netbox.dcim.device_bays.get(name='Bay 1')
+        >>> get_child_device = netbox.dcim.devices.get(name='child device')
+        >>> get_device_bay.installed_device = get_child_device
+        >>> get_device_bay.save()
+
+        Create a network interface
+
+        >>> interface = netbox.dcim.interfaces.get(name="interface-test", device="test-device")
+        >>> netbox_ip = netbox.ipam.ip_addresses.create(
+        ... address = "ip-address",
+        ... tenant = netbox.tenancy.tenants.get(name='tenant-1').id,
+        ... tags = [{'name':'Tag 1'}],
+        ... )
+        >>> #assign IP Address to device's network interface
+        >>> netbox_ip.assigned_object = interface
+        >>> netbox_ip.assigned_object_id = interface.id
+        >>> netbox_ip.assigned_object_type = 'dcim.interface'
+        >>> # add dns name to IP Address (optional)
+        >>> netbox_ip.dns_name = "test.dns.local"
+        >>> # save changes to IP Address
+        >>> netbox_ip.save()
+
         """
 
         req = Request(
             base=self.url,
             token=self.token,
-            session_key=self.session_key,
             http_session=self.api.http_session,
         ).post(args[0] if args else kwargs)
 
@@ -380,7 +477,6 @@ class Endpoint:
         req = Request(
             base=self.url,
             token=self.token,
-            session_key=self.session_key,
             http_session=self.api.http_session,
         ).patch(series)
 
@@ -445,7 +541,6 @@ class Endpoint:
         req = Request(
             base=self.url,
             token=self.token,
-            session_key=self.session_key,
             http_session=self.api.http_session,
         )
         return True if req.delete(data=[{"id": i} for i in cleaned_ids]) else False
@@ -461,7 +556,7 @@ class Endpoint:
 
         :Returns: Dict containing the available choices.
 
-        :Example (from NetBox 2.8.x):
+        :Example:
 
         >>> from pprint import pprint
         >>> pprint(nb.ipam.ip_addresses.choices())
@@ -476,7 +571,8 @@ class Endpoint:
          'status': [{'display_name': 'Active', 'value': 'active'},
                     {'display_name': 'Reserved', 'value': 'reserved'},
                     {'display_name': 'Deprecated', 'value': 'deprecated'},
-                    {'display_name': 'DHCP', 'value': 'dhcp'}]}
+                    {'display_name': 'DHCP', 'value': 'dhcp'},
+                    {'display_name': 'SLAAC', 'value': 'slaac'}]}
         >>>
         """
         if self._choices:
@@ -485,7 +581,6 @@ class Endpoint:
         req = Request(
             base=self.url,
             token=self.api.token,
-            private_key=self.api.private_key,
             http_session=self.api.http_session,
         ).options()
         try:
@@ -545,7 +640,6 @@ class Endpoint:
             filters=kwargs,
             base=self.url,
             token=self.token,
-            session_key=self.session_key,
             http_session=self.api.http_session,
         )
 
@@ -566,7 +660,6 @@ class DetailEndpoint:
         self.request_kwargs = dict(
             base=self.url,
             token=parent_obj.api.token,
-            session_key=parent_obj.api.session_key,
             http_session=parent_obj.api.http_session,
         )
 
diff --git a/pynetbox/core/query.py b/pynetbox/core/query.py
index af9daf4..84a823b 100644
--- a/pynetbox/core/query.py
+++ b/pynetbox/core/query.py
@@ -15,7 +15,6 @@ limitations under the License.
 """
 import concurrent.futures as cf
 import json
-from urllib.parse import urlencode
 
 
 def calc_pages(limit, count):
@@ -71,8 +70,7 @@ class AllocationError(Exception):
     """Allocation Exception
 
     Used with available-ips/available-prefixes when there is no
-    room for allocation and NetBox returns 204 No Content (before
-    NetBox 3.1.1) or 409 Conflict (since NetBox 3.1.1+).
+    room for allocation and NetBox returns 409 Conflict.
     """
 
     def __init__(self, req):
@@ -118,8 +116,6 @@ class Request:
         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 private_key: (str, optional) The user's private key as a
-        string.
     """
 
     def __init__(
@@ -131,8 +127,6 @@ class Request:
         offset=None,
         key=None,
         token=None,
-        private_key=None,
-        session_key=None,
         threading=False,
     ):
         """
@@ -145,15 +139,11 @@ class Request:
                 In (e.g. /api/dcim/devices/?name='test') 'name': 'test'
                 would be in the filters dict.
             key (int, optional): database id of the item being queried.
-            private_key (string, optional): The user's private key as a
-                string.
         """
         self.base = self.normalize_url(base)
         self.filters = filters or None
         self.key = key
         self.token = token
-        self.private_key = private_key
-        self.session_key = session_key
         self.http_session = http_session
         self.url = self.base if not key else "{}{}/".format(self.base, key)
         self.threading = threading
@@ -196,31 +186,6 @@ class Request:
         else:
             raise RequestError(req)
 
-    def get_session_key(self):
-        """Requests session key
-
-        Issues a GET request to the `get-session-key` endpoint for
-        subsequent use in requests from the `secrets` endpoint.
-
-        :Returns: String containing session key.
-        """
-        req = self.http_session.post(
-            "{}secrets/get-session-key/?preserve_key=True".format(self.base),
-            headers={
-                "accept": "application/json",
-                "authorization": "Token {}".format(self.token),
-                "Content-Type": "application/x-www-form-urlencoded",
-            },
-            data=urlencode({"private_key": self.private_key.strip("\n")}),
-        )
-        if req.ok:
-            try:
-                return req.json()["session_key"]
-            except json.JSONDecodeError:
-                raise ContentError(req)
-        else:
-            raise RequestError(req)
-
     def get_status(self):
         """Gets the status from /api/status/ endpoint in NetBox.
 
@@ -254,8 +219,6 @@ class Request:
 
         if self.token:
             headers["authorization"] = "Token {}".format(self.token)
-        if self.session_key:
-            headers["X-Session-Key"] = self.session_key
 
         params = {}
         if not url_override:
@@ -268,7 +231,7 @@ class Request:
             url_override or self.url, headers=headers, params=params, json=data
         )
 
-        if req.status_code in [204, 409] and verb == "post":
+        if req.status_code == 409 and verb == "post":
             raise AllocationError(req)
         if verb == "delete":
             if req.ok:
@@ -367,8 +330,7 @@ class Request:
     def put(self, data):
         """Makes PUT request.
 
-        Makes a PUT request to NetBox's API. Adds the session key to
-        headers if the `private_key` attribute was populated.
+        Makes a PUT request to NetBox's API.
 
         :param data: (dict) Contains a dict that will be turned into a
             json object and sent to the API.
@@ -381,13 +343,12 @@ class Request:
     def post(self, data):
         """Makes POST request.
 
-        Makes a POST request to NetBox's API. Adds the session key to
-        headers if the `private_key` attribute was populated.
+        Makes a POST request to NetBox's API.
 
         :param data: (dict) Contains a dict that will be turned into a
             json object and sent to the API.
         :raises: RequestError if req.ok returns false.
-        :raises: AllocationError if req.status_code is 204 (No Content)
+        :raises: AllocationError if req.status_code is 409 (Conflict)
             as with available-ips and available-prefixes when there is
             no room for the requested allocation.
         :raises: ContentError if response is not json.
diff --git a/pynetbox/core/response.py b/pynetbox/core/response.py
index 50579ea..fe67e18 100644
--- a/pynetbox/core/response.py
+++ b/pynetbox/core/response.py
@@ -58,9 +58,19 @@ def get_return(lookup, return_fields=None):
 
 
 def flatten_custom(custom_dict):
-    return {
-        k: v if not isinstance(v, dict) else v["value"] for k, v in custom_dict.items()
-    }
+    ret = {}
+
+    for k, val in custom_dict.items():
+        current_val = val
+
+        if isinstance(val, dict):
+            current_val = val.get("id", val)
+
+        if isinstance(val, list):
+            current_val = [v.get("id") if isinstance(v, dict) else v for v in val]
+
+        ret[k] = current_val
+    return ret
 
 
 class JsonField:
@@ -441,7 +451,6 @@ class Record:
             req = Request(
                 base=self.url,
                 token=self.api.token,
-                session_key=self.api.session_key,
                 http_session=self.api.http_session,
             )
             self._parse_values(next(req.get()))
@@ -554,7 +563,6 @@ class Record:
                 key=self.id,
                 base=self.endpoint.url,
                 token=self.api.token,
-                session_key=self.api.session_key,
                 http_session=self.api.http_session,
             )
             if req.patch(updates):
@@ -601,7 +609,6 @@ class Record:
             key=self.id,
             base=self.endpoint.url,
             token=self.api.token,
-            session_key=self.api.session_key,
             http_session=self.api.http_session,
         )
         return True if req.delete() else False
diff --git a/pynetbox/models/dcim.py b/pynetbox/models/dcim.py
index 43c5dde..5f21bdd 100644
--- a/pynetbox/models/dcim.py
+++ b/pynetbox/models/dcim.py
@@ -57,7 +57,6 @@ class TraceableRecord(Record):
             key=str(self.id) + "/trace",
             base=self.endpoint.url,
             token=self.api.token,
-            session_key=self.api.session_key,
             http_session=self.api.http_session,
         ).get()
 
diff --git a/pynetbox/models/ipam.py b/pynetbox/models/ipam.py
index 69eea52..d97d32c 100644
--- a/pynetbox/models/ipam.py
+++ b/pynetbox/models/ipam.py
@@ -148,8 +148,6 @@ class VlanGroups(Record):
         Returns a DetailEndpoint object that is the interface for
         viewing and creating VLANs inside a VLAN group.
 
-        Available since NetBox 3.2.0.
-
         :returns: :py:class:`.DetailEndpoint`
 
         :Examples:
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 8bfd5a1..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,4 +0,0 @@
-[egg_info]
-tag_build = 
-tag_date = 0
-
diff --git a/setup.py b/setup.py
index 9e1d429..c18c2dc 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
 setup(
     name="pynetbox",
     description="NetBox API client library",
-    url="https://github.com/netbox-community/netbox",
+    url="https://github.com/netbox-community/pynetbox",
     author="Zach Moody",
     author_email="zmoody@do.co",
     license="Apache2",
diff --git a/tests/fixtures/api/get_session_key.json b/tests/fixtures/api/get_session_key.json
deleted file mode 100644
index 6ed4a91..0000000
--- a/tests/fixtures/api/get_session_key.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    "session_key": "ansodicnaoiwenafoi="
-}
\ No newline at end of file
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
index c3e8d8c..61eef2d 100644
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -1,6 +1,5 @@
 import os
 
-from packaging import version
 import subprocess as subp
 import time
 import yaml
diff --git a/tests/test_api.py b/tests/test_api.py
index af41755..48a927c 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -9,7 +9,6 @@ host = "http://localhost:8000"
 
 def_kwargs = {
     "token": "abc123",
-    "private_key_file": "tests/fixtures/api/get_session_key.json",
 }
 
 # Keys are app names, values are arbitrarily selected endpoints
@@ -26,7 +25,7 @@ endpoints = {
 class ApiTestCase(unittest.TestCase):
     @patch(
         "requests.sessions.Session.post",
-        return_value=Response(fixture="api/get_session_key.json"),
+        return_value=Response(),
     )
     def test_get(self, *_):
         api = pynetbox.api(host, **def_kwargs)
@@ -34,7 +33,7 @@ class ApiTestCase(unittest.TestCase):
 
     @patch(
         "requests.sessions.Session.post",
-        return_value=Response(fixture="api/get_session_key.json"),
+        return_value=Response(),
     )
     def test_sanitize_url(self, *_):
         api = pynetbox.api("http://localhost:8000/", **def_kwargs)
diff --git a/tests/test_app.py b/tests/test_app.py
index 37669b9..398415a 100644
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -11,21 +11,6 @@ def_kwargs = {
 }
 
 
-class AppCustomChoicesTestCase(unittest.TestCase):
-    @patch(
-        "pynetbox.core.query.Request.get",
-        return_value={
-            "Testfield1": {"TF1_1": 1, "TF1_2": 2},
-            "Testfield2": {"TF2_1": 3, "TF2_2": 4},
-        },
-    )
-    def test_custom_choices(self, *_):
-        api = pynetbox.api(host, **def_kwargs)
-        choices = api.extras.custom_choices()
-        self.assertEqual(len(choices), 2)
-        self.assertEqual(sorted(choices.keys()), ["Testfield1", "Testfield2"])
-
-
 class AppConfigTestCase(unittest.TestCase):
     @patch(
         "pynetbox.core.query.Request.get",
@@ -52,20 +37,7 @@ class AppConfigTestCase(unittest.TestCase):
         )
 
 
-class PluginAppCustomChoicesTestCase(unittest.TestCase):
-    @patch(
-        "pynetbox.core.query.Request.get",
-        return_value={
-            "Testfield1": {"TF1_1": 1, "TF1_2": 2},
-            "Testfield2": {"TF2_1": 3, "TF2_2": 4},
-        },
-    )
-    def test_custom_choices(self, *_):
-        api = pynetbox.api(host, **def_kwargs)
-        choices = api.plugins.test_plugin.custom_choices()
-        self.assertEqual(len(choices), 2)
-        self.assertEqual(sorted(choices.keys()), ["Testfield1", "Testfield2"])
-
+class PluginAppTestCase(unittest.TestCase):
     @patch(
         "pynetbox.core.query.Request.get",
         return_value=[
diff --git a/tests/unit/test_response.py b/tests/unit/test_response.py
index e5ca623..68752ea 100644
--- a/tests/unit/test_response.py
+++ b/tests/unit/test_response.py
@@ -162,32 +162,7 @@ class RecordTestCase(unittest.TestCase):
         test = Record(test_values, None, None)
         self.assertEqual(dict(test), test_values)
 
-    def test_choices_idempotence_prev27(self):
-        test_values = {
-            "id": 123,
-            "choices_test": {
-                "value": 1,
-                "label": "test",
-            },
-        }
-        test = Record(test_values, None, None)
-        test.choices_test = 1
-        self.assertFalse(test._diff())
-
-    def test_choices_idempotence_v27(self):
-        test_values = {
-            "id": 123,
-            "choices_test": {
-                "value": "test",
-                "label": "test",
-                "id": 1,
-            },
-        }
-        test = Record(test_values, None, None)
-        test.choices_test = "test"
-        self.assertFalse(test._diff())
-
-    def test_choices_idempotence_v28(self):
+    def test_choices_idempotence(self):
         test_values = {
             "id": 123,
             "choices_test": {
diff --git a/tests/util.py b/tests/util.py
index 5cd4f93..8e38006 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -8,6 +8,8 @@ class Response:
         self.ok = ok
 
     def load_fixture(self, path):
+        if not path:
+            return "{}"
         with open("tests/fixtures/{}".format(path), "r") as f:
             return f.read()
 

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.1.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.1.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.1.egg-info/not-zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.1.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.1.egg-info/top_level.txt

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.0.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.0.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.0.egg-info/not-zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.0.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pynetbox-7.0.0.egg-info/top_level.txt

No differences were encountered in the control files

More details

Full run details