Metadata-Version: 2.4
Name: simple_netbox
Version: 0.3.2
Summary: Simple REST-client for Netbox
Home-page: https://github.com/jinjamator/simple_netbox
Author: Wilhelm Putz
Author-email: wilhelm.putz@cancom.com
License: ASL V2
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: System :: Installation/Setup
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: httpx>=0.23
Requires-Dist: python-status>=1.0.1
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

Introduction
==================

simple_netbox is a simplified REST Client for Netbox



Features
-----------------

simple_netbox has following features:
    * manage login
    * simple CRUD via ensure_exists and ensure_absent helper functions
    * auto add slug on creation of objects if not supplied
    * CRUD interface for all possible API URLs
    * create curl commands from all calls (for documentation purposes)
    * a query layer that paginates for you, with typed accessors for the common
      objects (devices, sites, tenants, interfaces, …)
    * an optional *scope* — implicit filters applied to every query, so a client
      pinned to one tenant cannot return another's objects

Installation
------------

Install simple_netbox by running:

.. code-block:: bash

    pip3 install simple_netbox


Examples
---------

CRUD a site
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: python
    
    from simple_netbox import NetboxClient
    import logging
    from getpass import getpass
    import secrets
    import string

    logger = logging.getLogger()
    logging.basicConfig(encoding="utf-8", level=logging.INFO)


    URL=input("Please Enter Netbox URL: ") or "http://localhost:8000"
    token=input("Please Enter the Netbox token: ") or "not set"


    nb = NetboxClient(URL,token=token,log_curl_commands=True)

    logging.info("list all sites")
    logging.info(nb.api.dcim.sites.list()) # alternativly nb.api.dcim.sites.get() can be used


    logging.info("create site demo1, slug will be autogenerated if not supplied") 

    site_id=nb.api.dcim.sites.create(body={"name":"demo1"})["id"] # alternativly nb.api.dcim.sites.post() can be used 

    logging.info("to filter results on server side following syntax can be used")

    logging.info(nb.api.dcim.sites.list(params={"name":"demo1"})) # alternativly nb.api.dcim.sites.get() can be used


    nb.api.dcim.sites.patch(site_id,body={"description":"demo1 desc"})

    logging.info(f"delete site demo1 (id:{site_id})") 

    nb.api.dcim.sites.delete(site_id)


    logging.info(f"create site demo2 via ensure_exists")

    nb.api.dcim.sites.ensure_exists(name="demo2")

    logging.info(f"update site demo2 via ensure_exists")

    nb.api.dcim.sites.ensure_exists(name="demo2", description="nice location")

    logging.info(f"delete site demo2 via ensure_absent")

    nb.api.dcim.sites.ensure_absent(name="demo2")

    print(nb.api.curl_commands)


API tokens
-----------------

NetBox 4.6 introduced *v2* API tokens, which are made of a public ``key`` and a
secret ``token`` and authenticate with a different header. Pass the ``key`` and
the client uses the v2 form; leave it out and the v1 form is used, so existing
code keeps working unchanged.

.. code-block:: python

    # v1 token  ->  Authorization: Token <token>
    nb = NetboxClient(URL, token=token)

    # v2 token  ->  Authorization: Bearer nbt_<key>.<token>
    nb = NetboxClient(URL, token=token, key=key)

    # the credential can also be replaced on an existing client
    nb.login(token, key)

A hyphen cannot appear in attribute syntax, so an endpoint like
``dcim/device-types`` is reached by *calling* the parent instead — resources and
the api object both accept a segment name, and the result chains like any other:

.. code-block:: python

    device_types = nb.api.dcim("device-types").get()
    nb.api("dcim")("device-types").get()          # the same, all the way down
    nb.api.dcim("device-types").trace             # calls chain into attributes

``add_resource(resource_name="dcim/device-types")`` registers the same path up
front and remains available, but nothing has to be registered before use.

The query layer below takes paths as strings, so it sidesteps the question
entirely — and, unlike attribute or call access, a segment sharing a name with a
resource attribute (``nb.api.dcim("get")`` is the HTTP action, not an endpoint)
still resolves to an endpoint there.

Querying
-----------------

``nb.api.<app>.<endpoint>`` is the raw CRUD interface: one request, one page.
The query layer on the client itself follows NetBox's pagination and returns
plain lists, so a query never silently stops at the first 50 objects:

.. code-block:: python

    nb.devices(role="access-switch", tag=["core", "edge"])   # every page
    nb.device(name="core-sw-01")
    nb.device(ip="10.0.0.1")        # resolved via the interface the address is on
    nb.interfaces(device=dev)       # a device dict, an id or a name
    nb.sites() / nb.tenants() / nb.tags() / nb.platforms() / nb.device_roles()
    nb.racks() / nb.ip_addresses()

    nb.get("ipam/vrfs", tenant="acme")   # any endpoint, still paginated
    nb.count("dcim/devices")             # without fetching them
    nb.status()                          # cheap connectivity + credential check

    nb.set_device_field(dev, custom_fields={"os_version": "17.9.4"})

Filter values are normalised: an object returned by an earlier query can be
passed straight back in (its slug is used), and a list becomes repeated
parameters, which NetBox ORs.

Scope
-----------------

A client may be bound to implicit filters applied to every query that can
express them:

.. code-block:: python

    nb = NetboxClient(URL, token=token, scope={"tenant": "acme", "status": "active"})

    nb.devices()                      # only acme's active devices
    nb.devices(tenant="other")        # an explicit filter always wins
    nb.devices(scope=False)           # the cross-tenant escape hatch
    nb.devices(scope={"site": "vie"}) # replace the scope for one call

Scope is applied through a per-endpoint table (``ENDPOINT_SCOPE_FILTERS``)
rather than blindly, because NetBox rejects a filter an endpoint does not know
and because a wrongly applied one would quietly return the wrong set. An
endpoint that is not in the table is queried unscoped.

Contribute
----------

- Issue Tracker: https://github.com/jinjamator/simple_netbox/issues
- Source Code: https://github.com/jinjamator/simple_netbox

Roadmap
-----------------

Selected Roadmap items:
    * add more documentation
    * add some more examples

For documentation please refer to https://simple_netbox.readthedocs.io/en/latest/

License
-----------------

This project is licensed under the Apache License Version 2.0
