Skip to content

Commit 881c01a

Browse files
author
zmoody
committed
Merge branch 'master' into master.tracing-fix
2 parents d718aca + 6016d97 commit 881c01a

19 files changed

+683
-1284
lines changed

.github/workflows/py2.yml

Lines changed: 0 additions & 30 deletions
This file was deleted.

pynetbox/core/app.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ class PluginsApp(object):
148148
def __init__(self, api):
149149
self.api = api
150150

151+
def __getstate__(self):
152+
return self.__dict__
153+
154+
def __setstate__(self, d):
155+
self.__dict__.update(d)
156+
151157
def __getattr__(self, name):
152158
return App(self.api, "plugins/{}".format(name))
153159

pynetbox/core/endpoint.py

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,9 @@
1414
limitations under the License.
1515
"""
1616
from pynetbox.core.query import Request, RequestError
17-
from pynetbox.core.response import Record
17+
from pynetbox.core.response import Record, RecordSet
1818

19-
RESERVED_KWARGS = ("id", "pk", "limit", "offset")
20-
21-
22-
def response_loader(req, return_obj, endpoint):
23-
if isinstance(req, list):
24-
return [return_obj(i, endpoint.api, endpoint) for i in req]
25-
return return_obj(req, endpoint.api, endpoint)
19+
RESERVED_KWARGS = ("offset",)
2620

2721

2822
class Endpoint(object):
@@ -77,11 +71,14 @@ def _lookup_ret_obj(self, name, model):
7771
ret = Record
7872
return ret
7973

80-
def all(self):
74+
def all(self, limit=0):
8175
"""Queries the 'ListView' of a given endpoint.
8276
8377
Returns all objects from an endpoint.
8478
79+
:arg int,optional limit: Overrides the max page size on
80+
paginated returns.
81+
8582
:Returns: List of :py:class:`.Record` objects.
8683
8784
:Examples:
@@ -96,9 +93,10 @@ def all(self):
9693
session_key=self.session_key,
9794
http_session=self.api.http_session,
9895
threading=self.api.threading,
96+
limit=limit,
9997
)
10098

101-
return response_loader(req.get(), self.return_obj, self)
99+
return RecordSet(self, req)
102100

103101
def get(self, *args, **kwargs):
104102
r"""Queries the DetailsView of a given endpoint.
@@ -135,17 +133,7 @@ def get(self, *args, **kwargs):
135133
key = None
136134

137135
if not key:
138-
filter_lookup = self.filter(**kwargs)
139-
if filter_lookup:
140-
if len(filter_lookup) > 1:
141-
raise ValueError(
142-
"get() returned more than one result. "
143-
"Check that the kwarg(s) passed are valid for this "
144-
"endpoint or use filter() or all() instead."
145-
)
146-
else:
147-
return filter_lookup[0]
148-
return None
136+
return next(iter(self.filter(**kwargs)), None)
149137

150138
req = Request(
151139
key=key,
@@ -154,17 +142,14 @@ def get(self, *args, **kwargs):
154142
session_key=self.session_key,
155143
http_session=self.api.http_session,
156144
)
157-
158145
try:
159-
resp = req.get()
146+
return next(iter(RecordSet(self, req)), None)
160147
except RequestError as e:
161148
if e.req.status_code == 404:
162149
return None
163150
else:
164151
raise e
165152

166-
return response_loader(resp, self.return_obj, self)
167-
168153
def filter(self, *args, **kwargs):
169154
r"""Queries the 'ListView' of a given endpoint.
170155
@@ -176,6 +161,8 @@ def filter(self, *args, **kwargs):
176161
accepted on given endpoint.
177162
:arg str,optional \**kwargs: Any search argument the
178163
endpoint accepts can be added as a keyword arg.
164+
:arg int,optional limit: Overrides the max page size on
165+
paginated returns.
179166
180167
:Returns: A list of :py:class:`.Record` objects.
181168
@@ -214,8 +201,8 @@ def filter(self, *args, **kwargs):
214201
raise ValueError("filter must be passed kwargs. Perhaps use all() instead.")
215202
if any(i in RESERVED_KWARGS for i in kwargs):
216203
raise ValueError(
217-
"A reserved {} kwarg was passed. Please remove it "
218-
"try again.".format(RESERVED_KWARGS)
204+
"A reserved kwarg was passed ({}). Please remove it "
205+
"and try again.".format(RESERVED_KWARGS)
219206
)
220207

221208
req = Request(
@@ -225,9 +212,10 @@ def filter(self, *args, **kwargs):
225212
session_key=self.session_key,
226213
http_session=self.api.http_session,
227214
threading=self.api.threading,
215+
limit=kwargs.get("limit", 0),
228216
)
229217

230-
return response_loader(req.get(), self.return_obj, self)
218+
return RecordSet(self, req)
231219

232220
def create(self, *args, **kwargs):
233221
r"""Creates an object on an endpoint.
@@ -287,7 +275,7 @@ def create(self, *args, **kwargs):
287275
http_session=self.api.http_session,
288276
).post(args[0] if args else kwargs)
289277

290-
return response_loader(req, self.return_obj, self)
278+
return self.return_obj(req, self.api, self)
291279

292280
def choices(self):
293281
""" Returns all choices from the endpoint.
@@ -425,7 +413,10 @@ def list(self, **kwargs):
425413
req = Request(**self.request_kwargs).get(add_params=kwargs)
426414

427415
if self.custom_return:
428-
return response_loader(req, self.custom_return, self.parent_obj.endpoint)
416+
for i in req:
417+
yield self.custom_return(
418+
i, self.parent_obj.endpoint.api, self.parent_obj.endpoint
419+
)
429420
return req
430421

431422
def create(self, data=None):
@@ -444,7 +435,9 @@ def create(self, data=None):
444435
data = data or {}
445436
req = Request(**self.request_kwargs).post(data)
446437
if self.custom_return:
447-
return response_loader(req, self.custom_return, self.parent_obj.endpoint)
438+
return self.custom_return(
439+
req, self.parent_obj.endpoint.api, self.parent_obj.endpoint
440+
)
448441
return req
449442

450443

pynetbox/core/query.py

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ def __init__(
130130
base,
131131
http_session,
132132
filters=None,
133+
limit=None,
133134
key=None,
134135
token=None,
135136
private_key=None,
@@ -158,6 +159,7 @@ def __init__(
158159
self.http_session = http_session
159160
self.url = self.base if not key else "{}{}/".format(self.base, key)
160161
self.threading = threading
162+
self.limit = limit
161163

162164
def get_openapi(self):
163165
""" Gets the OpenAPI Spec """
@@ -305,36 +307,12 @@ def get(self, add_params=None):
305307
endpoint.
306308
"""
307309

308-
def req_all():
309-
req = self._make_call(add_params=add_params)
310-
if isinstance(req, dict) and req.get("results") is not None:
311-
ret = req["results"]
312-
first_run = True
313-
while req["next"]:
314-
# Not worrying about making sure add_params kwargs is
315-
# passed in here because results from detail routes aren't
316-
# paginated, thus far.
317-
if first_run:
318-
req = self._make_call(
319-
add_params={
320-
"limit": req["count"],
321-
"offset": len(req["results"]),
322-
}
323-
)
324-
else:
325-
req = self._make_call(url_override=req["next"])
326-
first_run = False
327-
ret.extend(req["results"])
328-
return ret
329-
else:
330-
return req
331-
332-
def req_all_threaded(add_params):
333-
if add_params is None:
334-
# Limit must be 0 to discover the max page size
335-
add_params = {"limit": 0}
336-
req = self._make_call(add_params=add_params)
337-
if isinstance(req, dict) and req.get("results") is not None:
310+
if not add_params and self.limit is not None:
311+
add_params = {"limit": self.limit}
312+
req = self._make_call(add_params=add_params)
313+
if isinstance(req, dict) and req.get("results") is not None:
314+
self.count = req["count"]
315+
if self.threading:
338316
ret = req["results"]
339317
if req.get("next"):
340318
page_size = len(req["results"])
@@ -347,15 +325,30 @@ def req_all_threaded(add_params):
347325
ret.extend(req["results"])
348326
else:
349327
self.concurrent_get(ret, page_size, page_offsets)
350-
351-
return ret
352-
else:
353-
return req
354-
355-
if self.threading:
356-
return req_all_threaded(add_params)
357-
358-
return req_all()
328+
for i in ret:
329+
yield i
330+
first_run = True
331+
for i in req["results"]:
332+
yield i
333+
while req["next"]:
334+
# Not worrying about making sure add_params kwargs is
335+
# passed in here because results from detail routes aren't
336+
# paginated, thus far.
337+
if first_run:
338+
req = self._make_call(
339+
add_params={
340+
"limit": self.limit or req["count"],
341+
"offset": len(req["results"]),
342+
}
343+
)
344+
else:
345+
req = self._make_call(url_override=req["next"])
346+
first_run = False
347+
for i in req["results"]:
348+
yield i
349+
else:
350+
self.count = len(req)
351+
yield req
359352

360353
def put(self, data):
361354
"""Makes PUT request.
@@ -438,4 +431,6 @@ def get_count(self, *args, **kwargs):
438431
:returns: Int of number of objects query returned.
439432
"""
440433

441-
return self._make_call(add_params={"limit": 1})["count"]
434+
if not hasattr(self, "count"):
435+
self.count = self._make_call(add_params={"limit": 1})["count"]
436+
return self.count

pynetbox/core/response.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import pynetbox.core.app
2020
from six.moves.urllib.parse import urlsplit
21-
from pynetbox.core.query import Request
21+
from pynetbox.core.query import Request, RequestError
2222
from pynetbox.core.util import Hashabledict
2323

2424

@@ -70,6 +70,27 @@ class JsonField(object):
7070
_json_field = True
7171

7272

73+
class RecordSet(object):
74+
def __init__(self, endpoint, request, **kwargs):
75+
self.endpoint = endpoint
76+
self.request = request
77+
self.response = self.request.get()
78+
self._response_cache = []
79+
80+
def __iter__(self):
81+
for i in self._response_cache:
82+
yield self.endpoint.return_obj(i, self.endpoint.api, self.endpoint)
83+
for i in self.response:
84+
yield self.endpoint.return_obj(i, self.endpoint.api, self.endpoint)
85+
86+
def __len__(self):
87+
try:
88+
return self.request.count
89+
except AttributeError:
90+
self._response_cache.append(next(self.response))
91+
return self.request.count
92+
93+
7394
class Record(object):
7495
"""Create python objects from netbox API responses.
7596
@@ -311,7 +332,7 @@ def full_details(self):
311332
session_key=self.api.session_key,
312333
http_session=self.api.http_session,
313334
)
314-
self._parse_values(req.get())
335+
self._parse_values(next(req.get()))
315336
self.has_details = True
316337
return True
317338
return False

pynetbox/models/dcim.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,23 @@
2424

2525
class TraceableRecord(Record):
2626
def trace(self):
27-
req = Request(
28-
key=str(self.id) + "/trace",
29-
base=self.endpoint.url,
30-
token=self.api.token,
31-
session_key=self.api.session_key,
32-
http_session=self.api.http_session,
33-
)
27+
req = list(
28+
Request(
29+
key=str(self.id) + "/trace",
30+
base=self.endpoint.url,
31+
token=self.api.token,
32+
session_key=self.api.session_key,
33+
http_session=self.api.http_session,
34+
).get()
35+
)[0]
3436
uri_to_obj_class_map = {
3537
"dcim/cables": Cables,
3638
"dcim/front-ports": FrontPorts,
3739
"dcim/interfaces": Interfaces,
3840
"dcim/rear-ports": RearPorts,
3941
}
4042
ret = []
41-
for (termination_a_data, cable_data, termination_b_data) in req.get():
43+
for (termination_a_data, cable_data, termination_b_data) in req:
4244
this_hop_ret = []
4345
for hop_item_data in (termination_a_data, cable_data, termination_b_data):
4446
# if not fully terminated then some items will be None

requirements-dev.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
black==19.10b0
2-
Faker==6.5.*
32
pytest==6.2.*
43
pytest-docker==0.10.*

0 commit comments

Comments
 (0)