Skip to content

Commit

Permalink
Merge pull request #17 from digitalocean/dev-3.0
Browse files Browse the repository at this point in the history
Dev 3.0
  • Loading branch information
Zach Moody authored Nov 16, 2017
2 parents 55f15d2 + bf910f7 commit 77f4b12
Show file tree
Hide file tree
Showing 35 changed files with 751 additions and 73 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ install:

python:
- "2.7"
- "3.6"

script:
- nosetests
- python -m "nose"
- pep8 pynetbox

deploy:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![Build Status](https://travis-ci.org/digitalocean/pynetbox.svg?branch=master)](https://travis-ci.org/digitalocean/pynetbox)

# Pynetbox
Python API client library for [NetBox](https://github.com/digitalocean/netbox).

Expand Down
5 changes: 5 additions & 0 deletions docs/IPAM.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
IPAM
========

.. autoclass:: pynetbox.ipam.Prefixes
:members:
16 changes: 12 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#
import os
import sys
import pkg_resources
sys.path.insert(0, os.path.abspath('../'))


Expand Down Expand Up @@ -54,9 +55,9 @@
# built documents.
#
# The short X.Y version.
version = u'1.0.0'
version = pkg_resources.get_distribution("pynetbox").version
# The full version, including alpha/beta/rc tags.
release = u'1.0.0'
release = version

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down Expand Up @@ -93,8 +94,15 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# html_static_path = ['_static']

html_sidebars = {'**': [
'globaltoc.html',
'relations.html',
'sourcelink.html',
'searchbox.html'
]
}

# -- Options for HTMLHelp output ------------------------------------------

Expand Down
8 changes: 8 additions & 0 deletions docs/endpoint.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Endpoint
========

.. autoclass:: pynetbox.lib.endpoint.Endpoint
:members:

.. autoclass:: pynetbox.lib.endpoint.DetailEndpoint
:members:
29 changes: 5 additions & 24 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
:maxdepth: 2
:caption: Contents:

endpoint
response
request
IPAM


API
===
Expand All @@ -14,30 +19,6 @@ App

.. autoclass:: pynetbox.api.App

Endpoint
========

.. autoclass:: pynetbox.lib.endpoint.Endpoint
:members:

Response
========

.. autoclass:: pynetbox.lib.response.Record
:members:

.. autoclass:: pynetbox.lib.response.IPRecord
:members:

.. autoclass:: pynetbox.lib.response.BoolRecord
:members:

Request
========

.. autoclass:: pynetbox.lib.query.RequestError
:members:


Indices and tables
==================
Expand Down
5 changes: 5 additions & 0 deletions docs/request.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Request
========

.. autoclass:: pynetbox.lib.query.RequestError
:members:
8 changes: 8 additions & 0 deletions docs/response.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Response
========

.. autoclass:: pynetbox.lib.response.Record
:members:

.. autoclass:: pynetbox.lib.response.IPRecord
:members:
2 changes: 2 additions & 0 deletions pynetbox/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class Api(object):
* secrets
* tenancy
* extras
* virtualization
Calling any of these attributes will return
:py:class:`.App` which exposes endpoints as attributes.
Expand Down Expand Up @@ -115,3 +116,4 @@ def __init__(self, url, token=None, private_key=None,
self.secrets = App('secrets', api_kwargs=self.api_kwargs)
self.tenancy = App('tenancy', api_kwargs=self.api_kwargs)
self.extras = App('extras', api_kwargs=self.api_kwargs)
self.virtualization = App('virtualization', api_kwargs=self.api_kwargs)
69 changes: 69 additions & 0 deletions pynetbox/ipam.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
limitations under the License.
'''
from pynetbox.lib.response import IPRecord
from pynetbox.lib.endpoint import DetailEndpoint


class IpAddresses(IPRecord):
Expand All @@ -27,6 +28,74 @@ class Prefixes(IPRecord):
def __str__(self):
return str(self.prefix)

@property
def available_ips(self):
""" Represents the ``available-ips`` detail endpoint.
Returns a DetailEndpoint object that is the interface for
viewing and creating IP addresses inside a prefix.
:returns: :py:class:`.DetailEndpoint`
:Examples:
>>> prefix = nb.ipam.prefixes.get(24)
>>> prefix.available_ips.list()
[{u'vrf': None, u'family': 4, u'address': u'10.1.1.49/30'}...]
To create a single IP:
>>> prefix = nb.ipam.prefixes.get(24)
>>> prefix.available_ips.create()
{u'status': 1, u'description': u'', u'nat_inside': None...}
To create multiple IPs:
>>> prefix = nb.ipam.prefixes.get(24)
>>> create = prefix.available_ips.create([{} for i in range(2)])
>>> len(create)
2
"""
return DetailEndpoint(
'available-ips',
parent_obj=self,
)

@property
def available_prefixes(self):
''' Represents the ``available-prefixes`` detail endpoint.
Returns a DetailEndpoint object that is the interface for
viewing and creating prefixes inside a parent prefix.
Very similar to :py:meth:`~pynetbox.ipam.Prefixes.available_ips`
, except that dict (or list of dicts) passed to ``.create()``
needs to have a ``prefex_length`` key/value specifed.
:returns: :py:class:`.DetailEndpoint`
:Examples:
>>> prefix.available_prefixes.list()
[{u'prefix': u'10.1.1.44/30', u'vrf': None, u'family': 4}]
Creating a single child prefix:
>>> prefix = nb.ipam.prefixes.get(1)
>>> new_prefix = prefix.available_prefixes.create(
... {'prefix_length': 29}
...)
>>> new_prefix['prefix']
u'10.1.1.56/29'
'''
return DetailEndpoint(
'available-prefixes',
parent_obj=self,
)


class Aggregates(IPRecord):

Expand Down
112 changes: 84 additions & 28 deletions pynetbox/lib/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,34 +245,25 @@ def filter(self, *args, **kwargs):
CACHE[self.endpoint_name].extend(ret)
return ret

def _create(self, data):
"""Base create method
def create(self, *args, **kwargs):
"""Creates an object on an endpoint.
Used in .create() and .bulk_create()
"""
Allows for the creation of new objects on an endpoint. Named
arguments are converted to json properties, and a single object
is created. NetBox's bulk creation capabilities can be used by
passing a list of dictionaries as the first argument.
req = Request(
base=self.url,
token=self.token,
session_key=self.session_key,
version=self.version,
)
post = req.post(data)
if post:
return post['id']
else:
return False
.. note:
def create(self, **kwargs):
"""Creates an object on an endpoint.
Allows for the creation of new objects on an endpoint.
Named arguments are converted to json properties.
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.
:returns: Integer of created object's id.
:returns: A response from NetBox as a dictionary or list of
dictionaries.
:Examples:
Expand All @@ -284,14 +275,79 @@ def create(self, **kwargs):
... 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
... }
... ])
"""
return self._create(kwargs)

def bulk_create(self, create_list):
"""Bulk creation method.
return Request(
base=self.url,
token=self.token,
session_key=self.session_key,
version=self.version,
).post(args[0] if len(args) > 0 else kwargs)


class DetailEndpoint(object):
'''Enables read/write Operations on detail endpoints.
Endpoints like ``available-ips`` that are detail routes off
traditional endpoints are handled with this class.
'''

def __init__(self, name, parent_obj=None):
self.token = parent_obj.api_kwargs.get('token')
self.version = parent_obj.api_kwargs.get('version')
self.session_key = parent_obj.api_kwargs.get('session_key')
self.url = "{}/{}/{}/".format(
parent_obj.endpoint_meta.get('url'),
parent_obj.id,
name
)
self.request_kwargs = dict(
base=self.url,
token=self.token,
session_key=self.session_key,
version=self.version,
)

def list(self):
"""The view operation for a detail endpoint
Returns the response from NetBox for a detail endpoint.
:returns: A dictionary or list of dictionaries its retrieved
from NetBox.
"""
return Request(**self.request_kwargs).get()

def create(self, data={}):
"""The write operation for a detail endpoint.
Creates objects on a detail endpoint in NetBox.
: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.
Does the same as .create() only accepting a list of dictionaries
with the the same key/value pairs as kwargs.
:returns: A dictionary or list of dictionaries its created in
NetBox.
"""
for i in create_list:
self._create(i)
return Request(**self.request_kwargs).post(data)
12 changes: 10 additions & 2 deletions pynetbox/lib/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ def construct_url(input):
else:
return self.base

def normalize_url(self, url):
""" Builds a url for POST actions.
"""
if url[-1] != '/':
return "{}/".format(url)

return url

def get(self):
"""Makes a GET request.
Expand Down Expand Up @@ -187,7 +195,7 @@ def make_request(url):

def req_all(url):
req = make_request(url)
if req.get('results') is not None:
if isinstance(req, dict) and req.get('results') is not None:
ret = req['results']
first_run = True
while req['next']:
Expand Down Expand Up @@ -255,7 +263,7 @@ def post(self, data):
{'X-Session-Key': self.session_key}
)
req = requests.post(
"{}/".format(self.url),
self.normalize_url(self.url),
headers=headers,
data=json.dumps(data)
)
Expand Down
Loading

0 comments on commit 77f4b12

Please sign in to comment.