├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── config.yml │ ├── deprecation.yaml │ ├── documentation_change.yaml │ ├── feature_request.yaml │ └── housekeeping.yaml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build-mkdocs.yml │ ├── publish.yml │ └── py3.yml ├── .gitignore ├── .readthedocs.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── IPAM.md ├── advanced.md ├── branching.md ├── development │ ├── getting-started.md │ ├── index.md │ └── release-checklist.md ├── endpoint.md ├── index.md ├── request.md ├── response.md └── stylesheets │ └── extra.css ├── mkdocs.yml ├── pynetbox ├── __init__.py ├── core │ ├── __init__.py │ ├── api.py │ ├── app.py │ ├── endpoint.py │ ├── query.py │ ├── response.py │ └── util.py └── models │ ├── __init__.py │ ├── circuits.py │ ├── dcim.py │ ├── extras.py │ ├── ipam.py │ ├── mapper.py │ ├── users.py │ ├── virtualization.py │ └── wireless.py ├── requirements-dev.txt ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── conftest.py ├── fixtures ├── api │ └── token_provision.json ├── circuits │ ├── circuit.json │ ├── circuit_termination.json │ ├── circuit_terminations.json │ ├── circuit_type.json │ ├── circuit_types.json │ ├── circuits.json │ ├── provider.json │ └── providers.json ├── dcim │ ├── cable.json │ ├── cables.json │ ├── choices.json │ ├── console_port.json │ ├── console_port_template.json │ ├── console_port_templates.json │ ├── console_ports.json │ ├── console_server_port.json │ ├── console_server_port_template.json │ ├── console_server_port_templates.json │ ├── console_server_ports.json │ ├── device.json │ ├── device_bay.json │ ├── device_bay_template.json │ ├── device_bay_templates.json │ ├── device_bays.json │ ├── device_bulk_create.json │ ├── device_role.json │ ├── device_roles.json │ ├── device_type.json │ ├── device_types.json │ ├── devices.json │ ├── interface.json │ ├── interface_connection.json │ ├── interface_connections.json │ ├── interface_template.json │ ├── interface_templates.json │ ├── interface_trace.json │ ├── interfaces.json │ ├── interfaces_1.json │ ├── interfaces_2.json │ ├── inventory_item.json │ ├── inventory_items.json │ ├── manufacturer.json │ ├── manufacturers.json │ ├── napalm.json │ ├── platform.json │ ├── platforms.json │ ├── power_outlet.json │ ├── power_outlet_template.json │ ├── power_outlet_templates.json │ ├── power_outlets.json │ ├── power_port.json │ ├── power_port_template.json │ ├── power_port_templates.json │ ├── power_ports.json │ ├── rack.json │ ├── rack_group.json │ ├── rack_groups.json │ ├── rack_reservation.json │ ├── rack_reservations.json │ ├── rack_role.json │ ├── rack_roles.json │ ├── rack_u.json │ ├── racks.json │ ├── region.json │ ├── regions.json │ ├── site.json │ ├── sites.json │ ├── virtual_chassis_device.json │ └── virtual_chassis_devices.json ├── ipam │ ├── aggregate.json │ ├── aggregates.json │ ├── available-ips-post.json │ ├── available-ips.json │ ├── available-prefixes-post.json │ ├── available-prefixes.json │ ├── ip_address.json │ ├── ip_addresses.json │ ├── prefix.json │ ├── prefixes.json │ ├── rir.json │ ├── rirs.json │ ├── role.json │ ├── roles.json │ ├── vlan.json │ ├── vlan_group.json │ ├── vlan_groups.json │ ├── vlans.json │ ├── vrf.json │ └── vrfs.json ├── tenancy │ ├── tenant.json │ ├── tenant_group.json │ ├── tenant_groups.json │ └── tenants.json ├── users │ ├── group.json │ ├── groups.json │ ├── permission.json │ ├── permissions.json │ ├── unknown_model.json │ ├── user.json │ └── users.json ├── virtualization │ ├── cluster.json │ ├── cluster_group.json │ ├── cluster_groups.json │ ├── cluster_type.json │ ├── cluster_types.json │ ├── clusters.json │ ├── interface.json │ ├── interfaces.json │ ├── virtual_machine.json │ └── virtual_machines.json └── wireless │ ├── wireless_lan.json │ └── wireless_lans.json ├── integration ├── conftest.py ├── test_dcim.py └── test_ipam.py ├── test_api.py ├── test_app.py ├── test_circuits.py ├── test_tenancy.py ├── test_users.py ├── test_virtualization.py ├── test_wireless.py ├── unit ├── __init__.py ├── test_detailendpoint.py ├── test_endpoint.py ├── test_extras.py ├── test_query.py ├── test_request.py └── test_response.py └── util.py /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @arthanson @jnovinger @bctiemann @jeremystretch 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | description: Report a reproducible bug in the current release of pynetbox 4 | labels: ["type: bug", "status: needs triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **NOTE:** This form is only for reporting _reproducible bugs_ in a current pynetbox release. 10 | - type: input 11 | attributes: 12 | label: pynetbox version 13 | description: What version of pynetbox are you currently running? 14 | placeholder: v7.5.0 15 | validations: 16 | required: true 17 | - type: input 18 | attributes: 19 | label: NetBox version 20 | description: What version of NetBox are you currently running? 21 | placeholder: v4.3.1 22 | validations: 23 | required: true 24 | - type: dropdown 25 | attributes: 26 | label: Python version 27 | description: What version of Python are you currently running? 28 | options: 29 | - "3.10" 30 | - "3.11" 31 | - "3.12" 32 | validations: 33 | required: true 34 | - type: textarea 35 | attributes: 36 | label: Steps to Reproduce 37 | description: > 38 | Please provide a minimal working example to demonstrate the bug. Begin with the 39 | initialization of any necessary database objects and clearly enumerate each 40 | operation carried out. Ensure that your example is as concise as possible 41 | while adequately illustrating the issue. For example: 42 | ```python 43 | >>> import pynetbox 44 | >>> nb = pynebox.api('https://netbox.example.com', token='my-token') 45 | 46 | ``` 47 | Note: **do not utilize the demo instance** for replicating suspected bugs, 48 | as its data is subject to change or removal at any time. 49 | 50 | _Please refrain from including any confidential or sensitive 51 | information in your example._ 52 | validations: 53 | required: true 54 | - type: textarea 55 | attributes: 56 | label: Expected Behavior 57 | description: What did you expect to happen? 58 | placeholder: The script should execute without raising any errors or exceptions 59 | validations: 60 | required: true 61 | - type: textarea 62 | attributes: 63 | label: Observed Behavior 64 | description: What happened instead? 65 | placeholder: A TypeError exception was raised 66 | validations: 67 | required: true 68 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: 📖 Contributing Policy 5 | url: https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md 6 | about: "Please read through our contributing policy before opening an issue or pull request." 7 | - name: ❓ Discussion 8 | url: https://github.com/netbox-community/pynetbox/discussions 9 | about: "If you're just looking for help, try starting a discussion instead." 10 | - name: 💬 Community Slack 11 | url: https://netdev.chat 12 | about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems." 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/deprecation.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🗑️ Deprecation 3 | description: The removal of an existing feature or resource 4 | labels: ["type: deprecation"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Proposed Changes 9 | description: > 10 | Describe in detail the proposed changes. What is being removed? 11 | validations: 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: Justification 16 | description: Please provide justification for the proposed change(s). 17 | validations: 18 | required: true 19 | - type: textarea 20 | attributes: 21 | label: Impact 22 | description: List all areas of the application that will be affected by this change. 23 | validations: 24 | required: true 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_change.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📖 Documentation Change 3 | description: Suggest an addition or modification to the pynetbox documentation 4 | labels: ["type: documentation", "status: needs triage"] 5 | body: 6 | - type: dropdown 7 | attributes: 8 | label: Change Type 9 | description: What type of change are you proposing? 10 | options: 11 | - Addition 12 | - Correction 13 | - Removal 14 | - Cleanup (formatting, typos, etc.) 15 | validations: 16 | required: true 17 | - type: dropdown 18 | attributes: 19 | label: Area 20 | description: To what section of the documentation does this change primarily pertain? 21 | options: 22 | - Endpoint 23 | - Response 24 | - Request 25 | - IPAM 26 | - Other 27 | validations: 28 | required: true 29 | - type: textarea 30 | attributes: 31 | label: Proposed Changes 32 | description: Describe the proposed changes and why they are necessary. 33 | validations: 34 | required: true 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature Request 3 | description: Propose a new pynetbox feature or enhancement 4 | labels: ["type: feature", "status: needs triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **NOTE:** This form is only for submitting well-formed proposals to extend or modify 10 | pynetbox in some way. If you're trying to solve a problem but can't figure out how, or if 11 | you still need time to work on the details of a proposed new feature, please start a 12 | [discussion](https://github.com/netbox-community/pynetbox/discussions) instead. 13 | - type: input 14 | attributes: 15 | label: pynetbox version 16 | description: What version of pynetbox are you currently running? 17 | placeholder: v7.1.0 18 | validations: 19 | required: true 20 | - type: input 21 | attributes: 22 | label: NetBox version 23 | description: What version of NetBox are you currently running? 24 | placeholder: v3.6.0 25 | validations: 26 | required: true 27 | - type: dropdown 28 | attributes: 29 | label: Feature type 30 | options: 31 | - Data model extension 32 | - New functionality 33 | - Change to existing functionality 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: Proposed functionality 39 | description: > 40 | Describe in detail the new feature or behavior you are proposing. Include any specific changes 41 | to work flows, data models, and/or the user interface. The more detail you provide here, the 42 | greater chance your proposal has of being discussed. Feature requests which don't include an 43 | actionable implementation plan will be rejected. 44 | validations: 45 | required: true 46 | - type: textarea 47 | attributes: 48 | label: Use case 49 | description: > 50 | Explain how adding this functionality would benefit pynetbox users. What need does it address? 51 | validations: 52 | required: true 53 | - type: textarea 54 | attributes: 55 | label: External dependencies 56 | description: > 57 | List any new dependencies on external libraries or services that this new feature would 58 | introduce. For example, does the proposal require the installation of a new Python package? 59 | (Not all new features introduce new dependencies.) 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/housekeeping.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🏡 Housekeeping 3 | description: A change pertaining to the codebase itself (developers only) 4 | labels: ["type: housekeeping"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **NOTE:** This template is for use by maintainers only. Please do not submit 10 | an issue using this template unless you have been specifically asked to do so. 11 | - type: textarea 12 | attributes: 13 | label: Proposed Changes 14 | description: > 15 | Describe in detail the new feature or behavior you'd like to propose. 16 | Include any specific changes to work flows, data models, or the user interface. 17 | validations: 18 | required: true 19 | - type: textarea 20 | attributes: 21 | label: Justification 22 | description: Please provide justification for the proposed change(s). 23 | validations: 24 | required: true 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | ### Fixes: #1234 14 | 15 | 18 | -------------------------------------------------------------------------------- /.github/workflows/build-mkdocs.yml: -------------------------------------------------------------------------------- 1 | name: Build MkDocs 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-python@v4 15 | with: 16 | python-version: 3.x 17 | - run: pip install mkdocs-material mkdocs-autorefs mkdocs-material-extensions mkdocstrings mkdocstrings-python-legacy mkdocs-include-markdown-plugin pymdown-extensions markdown-include 18 | - run: mkdocs gh-deploy --force 19 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python -m build 35 | - name: Publish package 36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /.github/workflows/py3.yml: -------------------------------------------------------------------------------- 1 | name: Py3 Test 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python: ["3.10", "3.11", "3.12"] 16 | netbox: ["4.1", "4.2", "4.3"] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Setup Python 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: ${{ matrix.python }} 25 | 26 | - name: Install dev requirements 27 | run: pip install -r requirements-dev.txt . 28 | 29 | - name: Run Linter 30 | run: black --diff --check pynetbox tests 31 | 32 | - name: Run Tests 33 | run: pytest --netbox-versions=${{ matrix.netbox }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | lib/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *.cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # Jupyter Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # SageMath parsed files 79 | *.sage.py 80 | 81 | # Environments 82 | .env 83 | .venv 84 | env/ 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | .spyproject 91 | 92 | # Rope project settings 93 | .ropeproject 94 | 95 | # mkdocs documentation 96 | /site 97 | 98 | # mypy 99 | .mypy_cache/ 100 | 101 | # Other git repos checked out locally 102 | .netbox-docker-*/ 103 | .devicetype-library/ 104 | 105 | # Visual Studio Code settings 106 | .vscode/ 107 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | # Build from the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Explicitly set the version of Python and its requirements 13 | python: 14 | install: 15 | - requirements: docs/requirements.txt 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | For the list of changelog, please see the repository releases information in GitHub. 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pynetbox 2 | Python API client library for [NetBox](https://github.com/netbox-community/netbox). 3 | 4 | > **Note:** Version 6.7 and later of the library only supports NetBox 3.3 and above. 5 | 6 | ## Compatibility 7 | 8 | Each pyNetBox Version listed below has been tested with its corresponding NetBox Version. 9 | 10 | | NetBox Version | Plugin Version | 11 | |:--------------:|:--------------:| 12 | | 4.3 | 7.5.0 | 13 | | 4.2 | 7.5.0 | 14 | | 4.1 | 7.5.0 | 15 | | 4.0.6 | 7.4.1 | 16 | | 4.0.0 | 7.3.4 | 17 | | 3.7 | 7.3.0 | 18 | | 3.6 | 7.2.0 | 19 | | 3.5 | 7.1.0 | 20 | | 3.3 | 7.0.0 | 21 | 22 | ## Installation 23 | 24 | To install run `pip install pynetbox`. 25 | 26 | Alternatively, you can clone the repo and run `python setup.py install`. 27 | 28 | 29 | ## Quick Start 30 | 31 | The full pynetbox API is documented on [GitHub Pages](https://netbox-community.github.io/pynetbox/), but the following should be enough to get started using it. 32 | 33 | To begin, import pynetbox and instantiate the API. 34 | 35 | ``` 36 | import pynetbox 37 | nb = pynetbox.api( 38 | 'http://localhost:8000', 39 | token='d6f4e314a5b5fefd164995169f28ae32d987704f' 40 | ) 41 | ``` 42 | 43 | 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. 44 | 45 | 46 | ## Queries 47 | 48 | The pynetbox API is setup so that NetBox's apps are attributes of the `.api()` object, and in turn those apps have attribute representing each endpoint. Each endpoint has a handful of methods available to carry out actions on the endpoint. For example, in order to query all the objects in the `devices` endpoint you would do the following: 49 | 50 | ``` 51 | >>> devices = nb.dcim.devices.all() 52 | >>> for device in devices: 53 | ... print(device.name) 54 | ... 55 | test1-leaf1 56 | test1-leaf2 57 | test1-leaf3 58 | >>> 59 | ``` 60 | 61 | 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: 62 | ``` 63 | >>> devices = list(nb.dcim.devices.all()) 64 | ``` 65 | 66 | ### Threading 67 | 68 | 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`: 69 | 70 | ```python 71 | nb = pynetbox.api( 72 | 'http://localhost:8000', 73 | threading=True, 74 | ) 75 | ``` 76 | 77 | ## Running Tests 78 | 79 | First, create and activate a Python virtual environment in the pynetbox directory to isolate the project dependencies: 80 | 81 | ```python 82 | python3 -m venv venv 83 | source venv/bin/activate 84 | ``` 85 | 86 | Install both requirements files: 87 | 88 | ```python 89 | pip install -r requirements.txt 90 | pip install -r requirements-dev.txt 91 | ``` 92 | 93 | The test suite requires Docker to be installed and running, as it will download and launch netbox-docker containers during test execution. 94 | 95 | With Docker installed and running, execute the following command to run the test suite: 96 | 97 | ```python 98 | pytest 99 | ``` 100 | 101 | ## Alternative Library 102 | 103 | > **Note:** For those interested in a different approach, there is an alternative Python API client library available for NetBox called [netbox-python](https://github.com/netbox-community/netbox-python). This library provides a thin Python wrapper over the NetBox API. 104 | 105 | [netbox-python](https://github.com/netbox-community/netbox-python) offers a minimalistic interface to interact with NetBox's API. While it may not provide all the features available in pynetbox, it offers a lightweight and straightforward option for interfacing with NetBox. 106 | 107 | To explore further details and access the documentation, please visit the [netbox-python](https://github.com/netbox-community/netbox-python). 108 | -------------------------------------------------------------------------------- /docs/IPAM.md: -------------------------------------------------------------------------------- 1 | # IPAM 2 | 3 | ::: pynetbox.models.ipam.Prefixes 4 | handler: python 5 | options: 6 | members: true 7 | 8 | ::: pynetbox.models.ipam.VlanGroups 9 | handler: python 10 | options: 11 | members: true -------------------------------------------------------------------------------- /docs/advanced.md: -------------------------------------------------------------------------------- 1 | # Custom Sessions 2 | 3 | Custom sessions can be used to modify the default HTTP behavior. Below are a few examples, most of them from [here](https://hodovi.ch/blog/advanced-usage-python-requests-timeouts-retries-hooks/). 4 | 5 | ## Headers 6 | 7 | To set a custom header on all requests. These headers are automatically merged with headers pynetbox sets itself. 8 | 9 | Example: 10 | 11 | ```python 12 | import pynetbox 13 | import requests 14 | session = requests.Session() 15 | session.headers = {"mycustomheader": "test"} 16 | nb = pynetbox.api( 17 | 'http://localhost:8000', 18 | token='d6f4e314a5b5fefd164995169f28ae32d987704f' 19 | ) 20 | nb.http_session = session 21 | ``` 22 | 23 | ## SSL Verification 24 | 25 | To disable SSL verification. See [the docs](https://requests.readthedocs.io/en/stable/user/advanced/#ssl-cert-verification). 26 | 27 | Example: 28 | 29 | ```python 30 | import pynetbox 31 | import requests 32 | session = requests.Session() 33 | session.verify = False 34 | nb = pynetbox.api( 35 | 'http://localhost:8000', 36 | token='d6f4e314a5b5fefd164995169f28ae32d987704f' 37 | ) 38 | nb.http_session = session 39 | ``` 40 | 41 | ## Timeouts 42 | 43 | Setting timeouts requires the use of Adapters. 44 | 45 | Example: 46 | 47 | ```python 48 | from requests.adapters import HTTPAdapter 49 | 50 | class TimeoutHTTPAdapter(HTTPAdapter): 51 | def __init__(self, *args, **kwargs): 52 | self.timeout = kwargs.get("timeout", 5) 53 | super().__init__(*args, **kwargs) 54 | 55 | def send(self, request, **kwargs): 56 | kwargs['timeout'] = self.timeout 57 | return super().send(request, **kwargs) 58 | 59 | adapter = TimeoutHTTPAdapter() 60 | session = requests.Session() 61 | session.mount("http://", adapter) 62 | session.mount("https://", adapter) 63 | 64 | nb = pynetbox.api( 65 | 'http://localhost:8000', 66 | token='d6f4e314a5b5fefd164995169f28ae32d987704f' 67 | ) 68 | nb.http_session = session 69 | ``` -------------------------------------------------------------------------------- /docs/branching.md: -------------------------------------------------------------------------------- 1 | # Branching Plugin 2 | 3 | The NetBox branching plugin allows you to create and work with branches in NetBox, similar to version control systems. This enables you to make changes in isolation and merge them back to the main branch when ready. 4 | 5 | ## Activating Branches 6 | 7 | The `activate_branch` context manager allows you to perform operations within a specific branch's schema. All operations performed within the context manager will use that branch's schema. 8 | 9 | ```python 10 | import pynetbox 11 | 12 | # Initialize the API 13 | nb = pynetbox.api( 14 | "http://localhost:8000", 15 | token="your-token-here" 16 | ) 17 | 18 | # Get an existing branch 19 | branch = nb.plugins.branching.branches.get(id=1) 20 | 21 | # Activate the branch for operations 22 | with nb.activate_branch(branch): 23 | # All operations within this block will use the branch's schema 24 | sites = nb.dcim.sites.all() 25 | # Make changes to objects... 26 | # These changes will only exist in this branch 27 | ``` 28 | 29 | ## Waiting for Branch Status 30 | 31 | When working with branches, you often need to wait for certain status changes, such as when a branch becomes ready after creation or when a merge operation completes. The [tenacity](https://github.com/jd/tenacity) library provides a robust way to handle these waiting scenarios. 32 | 33 | First, install tenacity: 34 | 35 | ```bash 36 | pip install tenacity 37 | ``` 38 | 39 | Here's how to create a reusable function to wait for branch status changes: 40 | 41 | ```python 42 | from tenacity import retry, retry_if_result, stop_after_attempt, wait_exponential 43 | import pynetbox 44 | 45 | @retry( 46 | stop=stop_after_attempt(30), # Try for up to 30 attempts 47 | wait=wait_exponential( 48 | multiplier=1, min=4, max=60 49 | ), # Wait between 4-60 seconds, increasing exponentially 50 | retry=retry_if_result(lambda x: not x), # Retry if the status check returns False 51 | ) 52 | def wait_for_branch_status(branch, target_status): 53 | """Wait for branch to reach a specific status, with exponential backoff.""" 54 | branch = nb.plugins.branching.branches.get(branch.id) 55 | return str(branch.status) == target_status 56 | 57 | # Example usage: 58 | branch = nb.plugins.branching.branches.create(name="my-branch") 59 | 60 | # Wait for branch to be ready 61 | wait_for_branch_status(branch, "Ready") 62 | 63 | # Get the latest branch status 64 | branch = nb.plugins.branching.branches.get(branch.id) 65 | print(f"Branch is now ready! Status: {branch.status}") 66 | ``` 67 | 68 | The function will: 69 | 70 | 1. Check the current status of the branch 71 | 2. If the status doesn't match the target status, it will retry with exponential backoff 72 | 3. Continue retrying until either: 73 | - The branch reaches the target status 74 | - The maximum number of attempts (30) is reached 75 | - The maximum wait time (60 seconds) is exceeded 76 | 77 | The exponential backoff ensures that we don't overwhelm the server with requests while still checking frequently enough to catch status changes quickly. -------------------------------------------------------------------------------- /docs/development/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | This guide will help you get started with development on pynetbox. It covers setting up your development environment and running tests. 4 | 5 | ## Development Environment 6 | 7 | 1. Fork the pynetbox repository on GitHub 8 | 2. Clone your fork locally 9 | 3. Create a virtual environment and install development dependencies: 10 | 11 | ```bash 12 | python -m venv venv 13 | source venv/bin/activate # On Windows: venv\Scripts\activate 14 | pip install -e ".[dev]" 15 | ``` 16 | 17 | ## Running Tests 18 | 19 | pynetbox uses pytest for testing. The test suite includes both unit tests and integration tests. 20 | 21 | ### Unit Tests 22 | 23 | To run the unit tests: 24 | 25 | ```bash 26 | pytest tests/unit 27 | ``` 28 | 29 | ### Integration Tests 30 | 31 | The integration tests require a running NetBox instance. The test suite uses pytest-docker to spin up NetBox instances in Docker containers. 32 | 33 | To run the integration tests: 34 | 35 | ```bash 36 | pytest tests/integration 37 | ``` 38 | 39 | You can specify which versions of NetBox to test against using the `--netbox-versions` flag: 40 | 41 | ```bash 42 | pytest tests/integration --netbox-versions 4.1 4.2 4.3 43 | ``` 44 | 45 | ### Running Specific Tests 46 | 47 | You can run specific test files or test functions: 48 | 49 | ```bash 50 | # Run a specific test file 51 | pytest tests/unit/test_api.py 52 | 53 | # Run a specific test function 54 | pytest tests/unit/test_api.py::test_api_status 55 | 56 | # Run tests matching a pattern 57 | pytest -k "test_api" 58 | ``` 59 | 60 | ### Test Coverage 61 | 62 | To run tests with coverage reporting: 63 | 64 | ```bash 65 | pytest --cov=pynetbox tests/ 66 | ``` 67 | 68 | ## Submitting Pull Requests 69 | 70 | Once you're happy with your work and have verified that all tests pass, commit your changes and push it upstream to your fork. Always provide descriptive (but not excessively verbose) commit messages. Be sure to prefix your commit message with the word "Fixes" or "Closes" and the relevant issue number (with a hash mark). This tells GitHub to automatically close the referenced issue once the commit has been merged. 71 | 72 | ```bash 73 | git commit -m "Closes #1234: Add IPv5 support" 74 | git push origin 75 | ``` 76 | 77 | Once your fork has the new commit, submit a pull request to the pynetbox repo to propose the changes. Be sure to provide a detailed accounting of the changes being made and the reasons for doing so. 78 | 79 | Once submitted, a maintainer will review your pull request and either merge it or request changes. If changes are needed, you can make them via new commits to your fork: The pull request will update automatically. 80 | 81 | !!! warning 82 | Remember, pull requests are permitted only for **accepted** issues. If an issue you want to work on hasn't been approved by a maintainer yet, it's best to avoid risking your time and effort on a change that might not be accepted. (The one exception to this is trivial changes to the documentation or other non-critical resources.) -------------------------------------------------------------------------------- /docs/development/index.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | Thanks for your interest in contributing to pynetbox! This introduction covers a few important things to know before you get started. 4 | 5 | ## The Code 6 | 7 | pynetbox is maintained on [GitHub](https://github.com/netbox-community/pynetbox). GitHub also serves as one of our primary discussion forums. While all the code and discussion is publicly accessible, you'll need to register for a free GitHub account to engage in participation. Most people begin by forking the pynetbox repository under their own GitHub account to begin working on the code. 8 | 9 | There are two permanent branches in the repository: 10 | 11 | * `master` - Active development for the upcoming patch release. Pull requests will typically be based on this branch unless they introduce breaking changes that must be deferred until the next major release. 12 | * `feature` - New feature work to be introduced in the next major release. 13 | 14 | pynetbox components are arranged into modules: 15 | 16 | * `core/` - Core functionality including API interaction, response handling, and query building 17 | * `models/` - Model definitions for different NetBox object types 18 | * `tests/` - Test suite including unit and integration tests 19 | * `docs/` - Documentation files 20 | 21 | ## Proposing Changes 22 | 23 | All substantial changes made to the code base are tracked using GitHub issues. Feature requests, bug reports, and similar proposals must all be filed as issues and approved by a maintainer before work begins. This ensures that all changes to the code base are properly documented for future reference. 24 | 25 | To submit a new feature request or bug report for pynetbox, select and complete the appropriate issue template. Once your issue has been approved, you're welcome to submit a pull request containing your proposed changes. 26 | 27 | !!! note 28 | Avoid starting work on a proposal before it has been accepted. Not all proposed changes will be accepted, and we'd hate for you to waste time working on code that might not make it into the project. 29 | 30 | ## Getting Help 31 | 32 | There are two primary forums for getting assistance with pynetbox development: 33 | 34 | * [GitHub discussions](https://github.com/netbox-community/pynetbox/discussions) - The preferred forum for general discussion and support issues. Ideal for shaping a feature requests prior to submitting an issue. 35 | * [#netbox on NetDev Community Slack](https://netdev.chat) - Good for quick chats. Avoid any discussion that might need to be referenced later on, as the chat history is not retained indefinitely. 36 | 37 | !!! note 38 | Don't use GitHub issues to ask for help: These are reserved for proposed code changes only. 39 | 40 | ## Governance 41 | 42 | pynetbox follows the benevolent dictator model of governance, with the lead maintainer ultimately responsible for all changes to the code base. While community contributions are welcomed and encouraged, the lead maintainer's primary role is to ensure the project's long-term maintainability and continued focus on its primary functions. 43 | 44 | ## Licensing 45 | 46 | The entire pynetbox project is licensed as open source under the Apache 2.0 license. This is a very permissive license which allows unlimited redistribution of all code within the project. Note that all submissions to the project are subject to the same license. -------------------------------------------------------------------------------- /docs/development/release-checklist.md: -------------------------------------------------------------------------------- 1 | # Release Checklist 2 | 3 | This document outlines the steps required to prepare and publish a new release of pynetbox. 4 | 5 | ## Pre-Release Tasks 6 | 7 | 1. Ensure all tests are passing: 8 | ```bash 9 | pytest 10 | ``` 11 | 12 | 2. Update version number in `pynetbox/__init__.py` 13 | 3. Update documentation for any new features or changes 14 | 4. Check NetBox Docker releases: 15 | - Visit https://github.com/netbox-community/netbox-docker/releases 16 | - Review the latest NetBox Docker releases and their corresponding NetBox versions 17 | - Update supported NetBox versions in `tests/integration/conftest.py` if needed 18 | - Ensure the `get_netbox_docker_version_tag` function in `tests/integration/conftest.py` is updated with any new version mappings 19 | 20 | ## Release Tasks 21 | 22 | 1. Create a new release branch from `master`: 23 | ```bash 24 | git checkout master 25 | git pull 26 | git checkout -b release/vX.Y.Z 27 | ``` 28 | 29 | 2. Commit version and changelog updates: 30 | ```bash 31 | git commit -m "Prepare release vX.Y.Z" 32 | ``` 33 | 34 | 3. Create a pull request to merge the release branch into `master` 35 | 36 | 4. Once merged, use github to create a new release: 37 | 1. Go to the GitHub repository 38 | 2. Click "Releases" in the right sidebar 39 | 3. Click "Create a new release" 40 | 4. Create a new tag (e.g., vX.Y.Z) 41 | 5. Use the changelog content as the release description 42 | 6. Publish the release 43 | 44 | The GitHub release will automatically trigger the workflow to publish to PyPI. 45 | 46 | ## Supported NetBox Versions 47 | 48 | pynetbox aims to support the current and previous two minor versions of NetBox. The supported versions are defined in `tests/integration/conftest.py` and should be updated as part of the release process. -------------------------------------------------------------------------------- /docs/endpoint.md: -------------------------------------------------------------------------------- 1 | # Endpoint 2 | 3 | ::: pynetbox.core.endpoint.Endpoint 4 | handler: python 5 | options: 6 | members: true 7 | options: 8 | members: 9 | - all 10 | - choices 11 | - count 12 | - create 13 | - delete 14 | - filter 15 | - get 16 | - update 17 | show_source: true 18 | show_root_heading: true 19 | 20 | ::: pynetbox.core.endpoint.DetailEndpoint 21 | handler: python 22 | options: 23 | members: true 24 | options: 25 | members: 26 | - create 27 | - list 28 | show_source: true 29 | show_root_heading: true 30 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # PyNetBox Documentation 2 | 3 | ## TL;DR 4 | Instantiate the `Api` class. Use the methods available on `Endpoint` to return `Record` objects. 5 | 6 | ## API Reference 7 | 8 | ::: pynetbox.core.api.Api 9 | handler: python 10 | options: 11 | members: 12 | - __init__ 13 | - create_token 14 | - openapi 15 | - status 16 | - version 17 | - activate_branch 18 | show_source: true 19 | show_root_heading: true 20 | 21 | ## App Reference 22 | 23 | ::: pynetbox.core.app.App 24 | handler: python 25 | options: 26 | members: 27 | - config 28 | show_source: true 29 | show_root_heading: true -------------------------------------------------------------------------------- /docs/request.md: -------------------------------------------------------------------------------- 1 | # Request 2 | 3 | ::: pynetbox.core.query.RequestError 4 | handler: python 5 | options: 6 | members: false 7 | 8 | ::: pynetbox.core.query.ContentError 9 | handler: python 10 | options: 11 | members: false 12 | 13 | ::: pynetbox.core.query.AllocationError 14 | handler: python 15 | options: 16 | members: false -------------------------------------------------------------------------------- /docs/response.md: -------------------------------------------------------------------------------- 1 | # Response 2 | 3 | ::: pynetbox.core.response.Record 4 | handler: python 5 | options: 6 | members: 7 | - delete 8 | - full_details 9 | - save 10 | - serialize 11 | - update 12 | - updates 13 | show_source: true 14 | show_root_heading: true 15 | 16 | ::: pynetbox.core.response.RecordSet 17 | handler: python 18 | options: 19 | members: true 20 | options: 21 | members: 22 | - delete 23 | - update 24 | show_source: true 25 | show_root_heading: true 26 | -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | /* Custom styles for pynetbox documentation */ 2 | 3 | /* Improve code block appearance */ 4 | .md-typeset pre > code { 5 | padding: 1em; 6 | border-radius: 4px; 7 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 8 | } 9 | 10 | /* Style method signatures */ 11 | .md-typeset .doc-contents .doc-heading { 12 | border-bottom: 1px solid var(--md-default-fg-color--lightest); 13 | padding-bottom: 0.5em; 14 | margin-bottom: 1em; 15 | } 16 | 17 | /* Improve method documentation */ 18 | .md-typeset .doc-contents .doc-heading + p { 19 | margin-top: 1em; 20 | font-size: 0.9em; 21 | color: var(--md-default-fg-color--light); 22 | } 23 | 24 | /* Style parameters and returns sections */ 25 | .md-typeset .doc-contents .doc-heading + p + h3 { 26 | margin-top: 1.5em; 27 | font-size: 1.1em; 28 | color: var(--md-primary-fg-color); 29 | } 30 | 31 | /* Improve table appearance */ 32 | .md-typeset table { 33 | border-radius: 4px; 34 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 35 | } 36 | 37 | .md-typeset table th { 38 | background-color: var(--md-primary-fg-color); 39 | color: var(--md-primary-bg-color); 40 | } 41 | 42 | /* Style code examples */ 43 | .md-typeset .doc-contents .doc-heading + p + h3 + p + .highlight { 44 | margin: 1em 0; 45 | border-radius: 4px; 46 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 47 | } 48 | 49 | /* Improve navigation */ 50 | .md-nav__item .md-nav__link--active { 51 | color: var(--md-primary-fg-color); 52 | font-weight: bold; 53 | } 54 | 55 | /* Style admonitions */ 56 | .md-typeset .admonition { 57 | border-radius: 4px; 58 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 59 | } 60 | 61 | /* Improve method visibility */ 62 | .md-typeset .doc-contents .doc-heading { 63 | scroll-margin-top: 100px; 64 | } 65 | 66 | /* Style method parameters */ 67 | .md-typeset .doc-contents .doc-heading + p + h3 + p + ul { 68 | margin-left: 1em; 69 | padding-left: 1em; 70 | border-left: 2px solid var(--md-primary-fg-color); 71 | } 72 | 73 | /* Improve code block readability */ 74 | .md-typeset code { 75 | padding: 0.2em 0.4em; 76 | border-radius: 3px; 77 | background-color: var(--md-code-bg-color); 78 | box-shadow: 0 1px 2px rgba(0,0,0,0.1); 79 | } -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: pynetbox 2 | site_description: Python API client library for NetBox 3 | theme: 4 | name: material 5 | features: 6 | - navigation.tabs 7 | - navigation.sections 8 | - navigation.expand 9 | - navigation.top 10 | 11 | plugins: 12 | - search 13 | - mkdocstrings: 14 | default_handler: python 15 | handlers: 16 | python: 17 | options: 18 | show_source: true 19 | show_root_heading: true 20 | 21 | nav: 22 | - Home: index.md 23 | - API Reference: 24 | - Endpoint: endpoint.md 25 | - Response: response.md 26 | - Request: request.md 27 | - IPAM: IPAM.md 28 | - Advanced Topics: 29 | - Branching: branching.md 30 | - Advanced Usage: advanced.md 31 | - Development: 32 | - Development Guide: development/index.md 33 | - Getting Started: development/getting-started.md 34 | - Release Checklist: development/release-checklist.md 35 | 36 | extra_css: 37 | - stylesheets/extra.css 38 | 39 | markdown_extensions: 40 | - pymdownx.highlight: 41 | anchor_linenums: true 42 | line_spans: __span 43 | pygments_lang_class: true 44 | - pymdownx.inlinehilite 45 | - pymdownx.snippets 46 | - pymdownx.superfences 47 | - admonition 48 | - pymdownx.details 49 | - pymdownx.emoji 50 | -------------------------------------------------------------------------------- /pynetbox/__init__.py: -------------------------------------------------------------------------------- 1 | from pynetbox.core.api import Api as api 2 | from pynetbox.core.query import AllocationError, ContentError, RequestError 3 | 4 | __version__ = "7.5.0" 5 | -------------------------------------------------------------------------------- /pynetbox/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/pynetbox/c159a765fe8c56cdf3a1e276357ebd60867b0cea/pynetbox/core/__init__.py -------------------------------------------------------------------------------- /pynetbox/core/api.py: -------------------------------------------------------------------------------- 1 | """ 2 | (c) 2017 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | import contextlib 18 | 19 | import requests 20 | 21 | from pynetbox.core.app import App, PluginsApp 22 | from pynetbox.core.query import Request 23 | from pynetbox.core.response import Record 24 | 25 | 26 | class Api: 27 | """The API object is the point of entry to pynetbox. 28 | 29 | After instantiating the Api() with the appropriate named arguments 30 | you can specify which app and endpoint you wish to interact with. 31 | 32 | Valid attributes currently are: 33 | 34 | * circuits 35 | * core (NetBox 3.5+) 36 | * dcim 37 | * extras 38 | * ipam 39 | * tenancy 40 | * users 41 | * virtualization 42 | * vpn (NetBox 3.7+) 43 | * wireless 44 | 45 | Calling any of these attributes will return an `App` object which exposes endpoints as attributes. 46 | 47 | ## Additional Attributes 48 | 49 | * **http_session(requests.Session)**: Override the default session with your own. This is used to control 50 | a number of HTTP behaviors such as SSL verification, custom headers, 51 | retires, and timeouts. 52 | See [custom sessions](advanced.md#custom-sessions) for more info. 53 | 54 | ## Parameters 55 | 56 | * **url** (str): The base URL to the instance of NetBox you wish to connect to. 57 | * **token** (str): Your NetBox token. 58 | * **threading** (bool, optional): Set to True to use threading in `.all()` and `.filter()` requests. 59 | 60 | ## Raises 61 | 62 | * **AttributeError**: If app doesn't exist. 63 | 64 | ## Examples 65 | 66 | ```python 67 | import pynetbox 68 | nb = pynetbox.api( 69 | 'http://localhost:8000', 70 | token='d6f4e314a5b5fefd164995169f28ae32d987704f' 71 | ) 72 | list(nb.dcim.devices.all()) 73 | # [test1-leaf1, test1-leaf2, test1-leaf3] 74 | ``` 75 | """ 76 | 77 | def __init__( 78 | self, 79 | url, 80 | token=None, 81 | threading=False, 82 | ): 83 | """Initialize the API client. 84 | 85 | Args: 86 | url (str): The base URL to the instance of NetBox you wish to connect to. 87 | token (str, optional): Your NetBox API token. If not provided, authentication will be required for each request. 88 | threading (bool, optional): Set to True to use threading in `.all()` and `.filter()` requests, defaults to False. 89 | """ 90 | base_url = "{}/api".format(url if url[-1] != "/" else url[:-1]) 91 | self.token = token 92 | self.base_url = base_url 93 | self.http_session = requests.Session() 94 | self.threading = threading 95 | 96 | # Initialize NetBox apps 97 | self.circuits = App(self, "circuits") 98 | self.core = App(self, "core") 99 | self.dcim = App(self, "dcim") 100 | self.extras = App(self, "extras") 101 | self.ipam = App(self, "ipam") 102 | self.tenancy = App(self, "tenancy") 103 | self.users = App(self, "users") 104 | self.virtualization = App(self, "virtualization") 105 | self.vpn = App(self, "vpn") 106 | self.wireless = App(self, "wireless") 107 | self.plugins = PluginsApp(self) 108 | 109 | @property 110 | def version(self): 111 | """Gets the API version of NetBox. 112 | 113 | Can be used to check the NetBox API version if there are 114 | version-dependent features or syntaxes in the API. 115 | 116 | ## Returns 117 | Version number as a string. 118 | 119 | ## Example 120 | 121 | ```python 122 | import pynetbox 123 | nb = pynetbox.api( 124 | 'http://localhost:8000', 125 | token='d6f4e314a5b5fefd164995169f28ae32d987704f' 126 | ) 127 | nb.version 128 | # '3.1' 129 | ``` 130 | """ 131 | version = Request( 132 | base=self.base_url, 133 | token=self.token, 134 | http_session=self.http_session, 135 | ).get_version() 136 | return version 137 | 138 | def openapi(self): 139 | """Returns the OpenAPI spec. 140 | 141 | Quick helper function to pull down the entire OpenAPI spec. 142 | 143 | ## Returns 144 | dict: The OpenAPI specification as a dictionary. 145 | 146 | ## Example 147 | 148 | ```python 149 | import pynetbox 150 | nb = pynetbox.api( 151 | 'http://localhost:8000', 152 | token='d6f4e314a5b5fefd164995169f28ae32d987704f' 153 | ) 154 | nb.openapi() 155 | # {...} 156 | ``` 157 | """ 158 | return Request( 159 | base=self.base_url, 160 | http_session=self.http_session, 161 | ).get_openapi() 162 | 163 | def status(self): 164 | """Gets the status information from NetBox. 165 | 166 | ## Returns 167 | Dictionary containing NetBox status information. 168 | 169 | ## Raises 170 | `RequestError`: If the request is not successful. 171 | 172 | ## Example 173 | 174 | ```python 175 | from pprint import pprint 176 | pprint(nb.status()) 177 | { 178 | 'django-version': '3.1.3', 179 | 'installed-apps': { 180 | 'cacheops': '5.0.1', 181 | 'debug_toolbar': '3.1.1', 182 | 'django_filters': '2.4.0', 183 | 'django_prometheus': '2.1.0', 184 | 'django_rq': '2.4.0', 185 | 'django_tables2': '2.3.3', 186 | 'drf_yasg': '1.20.0', 187 | 'mptt': '0.11.0', 188 | 'rest_framework': '3.12.2', 189 | 'taggit': '1.3.0', 190 | 'timezone_field': '4.0' 191 | }, 192 | 'netbox-version': '2.10.2', 193 | 'plugins': {}, 194 | 'python-version': '3.7.3', 195 | 'rq-workers-running': 1 196 | } 197 | ``` 198 | """ 199 | status = Request( 200 | base=self.base_url, 201 | token=self.token, 202 | http_session=self.http_session, 203 | ).get_status() 204 | return status 205 | 206 | def create_token(self, username, password): 207 | """Creates an API token using a valid NetBox username and password. 208 | Saves the created token automatically in the API object. 209 | 210 | ## Parameters 211 | * **username** (str): NetBox username 212 | * **password** (str): NetBox password 213 | 214 | ## Returns 215 | `Record`: The token as a Record object. 216 | 217 | ## Raises 218 | `RequestError`: If the request is not successful. 219 | 220 | ## Example 221 | 222 | ```python 223 | import pynetbox 224 | nb = pynetbox.api("https://netbox-server") 225 | token = nb.create_token("admin", "netboxpassword") 226 | nb.token 227 | # '96d02e13e3f1fdcd8b4c089094c0191dcb045bef' 228 | 229 | from pprint import pprint 230 | pprint(dict(token)) 231 | { 232 | 'created': '2021-11-27T11:26:49.360185+02:00', 233 | 'description': '', 234 | 'display': '045bef (admin)', 235 | 'expires': None, 236 | 'id': 2, 237 | 'key': '96d02e13e3f1fdcd8b4c089094c0191dcb045bef', 238 | 'url': 'https://netbox-server/api/users/tokens/2/', 239 | 'user': { 240 | 'display': 'admin', 241 | 'id': 1, 242 | 'url': 'https://netbox-server/api/users/users/1/', 243 | 'username': 'admin' 244 | }, 245 | 'write_enabled': True 246 | } 247 | ``` 248 | """ 249 | resp = Request( 250 | base="{}/users/tokens/provision/".format(self.base_url), 251 | http_session=self.http_session, 252 | ).post(data={"username": username, "password": password}) 253 | # Save the newly created API token, otherwise populating the Record 254 | # object details will fail 255 | self.token = resp["key"] 256 | return Record(resp, self, None) 257 | 258 | @contextlib.contextmanager 259 | def activate_branch(self, branch): 260 | """Context manager to activate the branch by setting the schema ID in the headers. 261 | 262 | **Note**: The NetBox branching plugin must be installed and enabled in your NetBox instance for this functionality to work. 263 | 264 | ## Parameters 265 | * **branch** (Record): The NetBox branch to activate 266 | 267 | ## Raises 268 | `ValueError`: If the branch is not a valid NetBox branch. 269 | 270 | ## Example 271 | 272 | ```python 273 | import pynetbox 274 | nb = pynetbox.api("https://netbox-server") 275 | branch = nb.plugins.branching.branches.create(name="testbranch") 276 | with nb.activate_branch(branch): 277 | sites = nb.dcim.sites.all() 278 | # All operations within this block will use the branch's schema 279 | ``` 280 | """ 281 | if not isinstance(branch, Record) or not "schema_id" in dict(branch): 282 | raise ValueError( 283 | f"The specified branch is not a valid NetBox branch: {branch}." 284 | ) 285 | 286 | self.http_session.headers["X-NetBox-Branch"] = branch.schema_id 287 | 288 | try: 289 | yield 290 | finally: 291 | self.http_session.headers.pop("X-NetBox-Branch", None) 292 | -------------------------------------------------------------------------------- /pynetbox/core/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | (c) 2017 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from pynetbox.core.endpoint import Endpoint 18 | from pynetbox.core.query import Request 19 | from pynetbox.models import ( 20 | circuits, 21 | dcim, 22 | extras, 23 | ipam, 24 | users, 25 | virtualization, 26 | wireless, 27 | ) 28 | 29 | 30 | class App: 31 | """Represents apps in NetBox. 32 | 33 | Calls to attributes are returned as Endpoint objects. 34 | 35 | ## Returns 36 | Endpoint matching requested attribute. 37 | 38 | ## Raises 39 | RequestError if requested endpoint doesn't exist. 40 | """ 41 | 42 | def __init__(self, api, name): 43 | self.api = api 44 | self.name = name 45 | self._setmodel() 46 | 47 | models = { 48 | "dcim": dcim, 49 | "ipam": ipam, 50 | "circuits": circuits, 51 | "virtualization": virtualization, 52 | "extras": extras, 53 | "users": users, 54 | "wireless": wireless, 55 | } 56 | 57 | def _setmodel(self): 58 | self.model = App.models[self.name] if self.name in App.models else None 59 | 60 | def __getstate__(self): 61 | return {"api": self.api, "name": self.name} 62 | 63 | def __setstate__(self, d): 64 | self.__dict__.update(d) 65 | self._setmodel() 66 | 67 | def __getattr__(self, name): 68 | return Endpoint(self.api, self, name, model=self.model) 69 | 70 | def config(self): 71 | """Returns config response from app. 72 | 73 | ## Returns 74 | Raw response from NetBox's config endpoint. 75 | 76 | ## Raises 77 | RequestError if called for an invalid endpoint. 78 | 79 | ## Examples 80 | 81 | ```python 82 | pprint.pprint(nb.users.config()) 83 | { 84 | 'tables': { 85 | 'DeviceTable': { 86 | 'columns': [ 87 | 'name', 88 | 'status', 89 | 'tenant', 90 | 'role', 91 | 'site', 92 | 'primary_ip', 93 | 'tags' 94 | ] 95 | } 96 | } 97 | } 98 | ``` 99 | """ 100 | config = Request( 101 | base="{}/{}/config/".format( 102 | self.api.base_url, 103 | self.name, 104 | ), 105 | token=self.api.token, 106 | http_session=self.api.http_session, 107 | ).get() 108 | return config 109 | 110 | 111 | class PluginsApp: 112 | """Basically valid plugins api could be handled by same App class, 113 | but you need to add plugins to request url path. 114 | 115 | ## Returns 116 | App with added plugins into path. 117 | """ 118 | 119 | def __init__(self, api): 120 | self.api = api 121 | 122 | def __getstate__(self): 123 | return self.__dict__ 124 | 125 | def __setstate__(self, d): 126 | self.__dict__.update(d) 127 | 128 | def __getattr__(self, name): 129 | return App(self.api, "plugins/{}".format(name.replace("_", "-"))) 130 | 131 | def installed_plugins(self): 132 | """Returns raw response with installed plugins. 133 | 134 | ## Returns 135 | Raw response NetBox's installed plugins. 136 | 137 | ## Examples 138 | 139 | ```python 140 | nb.plugins.installed_plugins() 141 | [ 142 | { 143 | 'name': 'test_plugin', 144 | 'package': 'test_plugin', 145 | 'author': 'Dmitry', 146 | 'description': 'Netbox test plugin', 147 | 'verison': '0.10' 148 | } 149 | ] 150 | ``` 151 | """ 152 | installed_plugins = Request( 153 | base="{}/plugins/installed-plugins".format( 154 | self.api.base_url, 155 | ), 156 | token=self.api.token, 157 | http_session=self.api.http_session, 158 | ).get() 159 | return installed_plugins 160 | -------------------------------------------------------------------------------- /pynetbox/core/util.py: -------------------------------------------------------------------------------- 1 | class Hashabledict(dict): 2 | def __hash__(self): 3 | return hash(frozenset(self)) 4 | -------------------------------------------------------------------------------- /pynetbox/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/pynetbox/c159a765fe8c56cdf3a1e276357ebd60867b0cea/pynetbox/models/__init__.py -------------------------------------------------------------------------------- /pynetbox/models/circuits.py: -------------------------------------------------------------------------------- 1 | """ 2 | (c) 2017 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from pynetbox.core.response import Record 18 | 19 | 20 | class Circuits(Record): 21 | def __str__(self): 22 | return self.cid 23 | 24 | 25 | class CircuitTerminations(Record): 26 | def __str__(self): 27 | return self.circuit.cid 28 | -------------------------------------------------------------------------------- /pynetbox/models/dcim.py: -------------------------------------------------------------------------------- 1 | """ 2 | (c) 2017 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from urllib.parse import urlsplit 18 | 19 | from pynetbox.core.endpoint import DetailEndpoint, RODetailEndpoint 20 | from pynetbox.core.query import Request 21 | from pynetbox.core.response import JsonField, Record 22 | from pynetbox.models.circuits import Circuits, CircuitTerminations 23 | from pynetbox.models.ipam import IpAddresses 24 | 25 | 26 | class TraceableRecord(Record): 27 | def _get_obj_class(self, url): 28 | uri_to_obj_class_map = { 29 | "circuits/circuit-terminations": CircuitTerminations, 30 | "dcim/cables": Cables, 31 | "dcim/console-ports": ConsolePorts, 32 | "dcim/console-server-ports": ConsoleServerPorts, 33 | "dcim/front-ports": FrontPorts, 34 | "dcim/interfaces": Interfaces, 35 | "dcim/power-feeds": PowerFeeds, 36 | "dcim/power-outlets": PowerOutlets, 37 | "dcim/power-ports": PowerPorts, 38 | "dcim/rear-ports": RearPorts, 39 | } 40 | 41 | # the url for this item will be something like: 42 | # https://netbox/api/dcim/rear-ports/12761/ 43 | # TODO: Move this to a more general function. 44 | app_endpoint = "/".join( 45 | urlsplit(url).path[len(urlsplit(self.api.base_url).path) :].split("/")[1:3] 46 | ) 47 | return uri_to_obj_class_map.get( 48 | app_endpoint, 49 | Record, 50 | ) 51 | 52 | def _build_termination_data(self, termination_list): 53 | terminations_data = [] 54 | for hop_item_data in termination_list: 55 | return_obj_class = self._get_obj_class(hop_item_data["url"]) 56 | terminations_data.append( 57 | return_obj_class(hop_item_data, self.endpoint.api, self.endpoint) 58 | ) 59 | 60 | return terminations_data 61 | 62 | def trace(self): 63 | req = Request( 64 | key=str(self.id) + "/trace", 65 | base=self.endpoint.url, 66 | token=self.api.token, 67 | http_session=self.api.http_session, 68 | ).get() 69 | 70 | ret = [] 71 | for a_terminations_data, cable_data, b_terminations_data in req: 72 | ret.append(self._build_termination_data(a_terminations_data)) 73 | if not cable_data: 74 | ret.append(cable_data) 75 | else: 76 | return_obj_class = self._get_obj_class(cable_data["url"]) 77 | ret.append( 78 | return_obj_class(cable_data, self.endpoint.api, self.endpoint) 79 | ) 80 | ret.append(self._build_termination_data(b_terminations_data)) 81 | 82 | return ret 83 | 84 | 85 | class DeviceTypes(Record): 86 | def __str__(self): 87 | return self.model 88 | 89 | 90 | class Devices(Record): 91 | """Devices Object. 92 | 93 | Represents a device response from netbox. 94 | 95 | ## Attributes 96 | * **primary_ip, ip4, ip6** (list): Tells __init__ in Record() to 97 | take the `primary_ip` field's value from the API 98 | response and return an initialized list of IpAddress 99 | objects 100 | * **device_type** (obj): Tells __init__ in Record() to take the 101 | `device_type` field's value from the API response and 102 | return an initialized DeviceType object 103 | """ 104 | 105 | has_details = True 106 | device_type = DeviceTypes 107 | primary_ip = IpAddresses 108 | primary_ip4 = IpAddresses 109 | primary_ip6 = IpAddresses 110 | local_context_data = JsonField 111 | config_context = JsonField 112 | 113 | @property 114 | def napalm(self): 115 | """Represents the ``napalm`` detail endpoint. 116 | 117 | Returns a DetailEndpoint object that is the interface for 118 | viewing response from the napalm endpoint. 119 | 120 | ## Returns 121 | DetailEndpoint object. 122 | 123 | ## Examples 124 | 125 | ```python 126 | device = nb.ipam.devices.get(123) 127 | device.napalm.list(method='get_facts') 128 | # {"get_facts": {"interface_list": ["ge-0/0/0"]}} 129 | ``` 130 | """ 131 | return RODetailEndpoint(self, "napalm") 132 | 133 | @property 134 | def render_config(self): 135 | """Represents the ``render-config`` detail endpoint. 136 | 137 | Returns a DetailEndpoint object that is the interface for 138 | viewing response from the render-config endpoint. 139 | 140 | ## Returns 141 | DetailEndpoint object. 142 | 143 | ## Examples 144 | 145 | ```python 146 | device = nb.ipam.devices.get(123) 147 | device.render_config.create() 148 | ``` 149 | """ 150 | return DetailEndpoint(self, "render-config") 151 | 152 | 153 | class InterfaceConnections(Record): 154 | def __str__(self): 155 | return self.interface_a.name 156 | 157 | 158 | class InterfaceConnection(Record): 159 | def __str__(self): 160 | return self.interface.name 161 | 162 | 163 | class Interfaces(TraceableRecord): 164 | interface_connection = InterfaceConnection 165 | 166 | 167 | class PowerFeeds(TraceableRecord): 168 | pass 169 | 170 | 171 | class PowerOutlets(TraceableRecord): 172 | device = Devices 173 | 174 | 175 | class PowerPorts(TraceableRecord): 176 | device = Devices 177 | 178 | 179 | class ConsolePorts(TraceableRecord): 180 | device = Devices 181 | 182 | 183 | class ConsoleServerPorts(TraceableRecord): 184 | device = Devices 185 | 186 | 187 | class RackReservations(Record): 188 | def __str__(self): 189 | return self.description 190 | 191 | 192 | class VirtualChassis(Record): 193 | master = Devices 194 | 195 | 196 | class RUs(Record): 197 | device = Devices 198 | 199 | 200 | class FrontPorts(TraceableRecord): 201 | device = Devices 202 | 203 | 204 | class RearPorts(TraceableRecord): 205 | device = Devices 206 | 207 | 208 | class Racks(Record): 209 | @property 210 | def units(self): 211 | """Represents the ``units`` detail endpoint. 212 | 213 | Returns a DetailEndpoint object that is the interface for 214 | viewing response from the units endpoint. 215 | 216 | ## Returns 217 | DetailEndpoint object. 218 | 219 | ## Examples 220 | 221 | ```python 222 | rack = nb.dcim.racks.get(123) 223 | rack.units.list() 224 | # {"get_facts": {"interface_list": ["ge-0/0/0"]}} 225 | ``` 226 | """ 227 | return RODetailEndpoint(self, "units", custom_return=RUs) 228 | 229 | @property 230 | def elevation(self): 231 | """Represents the ``elevation`` detail endpoint. 232 | 233 | Returns a DetailEndpoint object that is the interface for 234 | viewing response from the elevation endpoint updated in 235 | Netbox version 2.8. 236 | 237 | ## Returns 238 | DetailEndpoint object. 239 | 240 | ## Examples 241 | 242 | ```python 243 | rack = nb.dcim.racks.get(123) 244 | rack.elevation.list() 245 | # {"get_facts": {"interface_list": ["ge-0/0/0"]}} 246 | ``` 247 | """ 248 | return RODetailEndpoint(self, "elevation", custom_return=RUs) 249 | 250 | 251 | class Termination(Record): 252 | def __str__(self): 253 | # hacky check to see if we're a circuit termination to 254 | # avoid another call to NetBox because of a non-existent attr 255 | # in self.name 256 | if "circuit" in str(self.url): 257 | return self.circuit.cid 258 | 259 | return self.name 260 | 261 | device = Devices 262 | circuit = Circuits 263 | 264 | 265 | class Cables(Record): 266 | def __str__(self): 267 | if len(self.a_terminations) == 1 and len(self.b_terminations) == 1: 268 | return "{} <> {}".format(self.a_terminations[0], self.b_terminations[0]) 269 | return "Cable #{}".format(self.id) 270 | -------------------------------------------------------------------------------- /pynetbox/models/extras.py: -------------------------------------------------------------------------------- 1 | """ 2 | (c) 2017 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from pynetbox.core.response import JsonField, Record 18 | 19 | 20 | class ConfigContexts(Record): 21 | data = JsonField 22 | 23 | 24 | class ObjectChanges(Record): 25 | object_data = JsonField 26 | postchange_data = JsonField 27 | prechange_data = JsonField 28 | 29 | def __str__(self): 30 | return self.request_id 31 | -------------------------------------------------------------------------------- /pynetbox/models/ipam.py: -------------------------------------------------------------------------------- 1 | """ 2 | (c) 2017 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from pynetbox.core.endpoint import DetailEndpoint 18 | from pynetbox.core.response import Record 19 | 20 | 21 | class IpAddresses(Record): 22 | def __str__(self): 23 | return str(self.address) 24 | 25 | 26 | class IpRanges(Record): 27 | def __str__(self): 28 | return str(self.display) 29 | 30 | @property 31 | def available_ips(self): 32 | """Represents the ``available-ips`` detail endpoint. 33 | 34 | Returns a DetailEndpoint object that is the interface for 35 | viewing and creating IP addresses inside an ip range. 36 | 37 | ## Returns 38 | DetailEndpoint object. 39 | 40 | ## Examples 41 | 42 | ```python 43 | ip_range = nb.ipam.ip_ranges.get(24) 44 | ip_range.available_ips.list() 45 | # [10.0.0.1/24, 10.0.0.2/24, 10.0.0.3/24, 10.0.0.4/24, 10.0.0.5/24, ...] 46 | ``` 47 | 48 | To create a single IP: 49 | 50 | ```python 51 | ip_range = nb.ipam.ip_ranges.get(24) 52 | ip_range.available_ips.create() 53 | # 10.0.0.1/24 54 | ``` 55 | 56 | To create multiple IPs: 57 | 58 | ```python 59 | ip_range = nb.ipam.ip_ranges.get(24) 60 | create = ip_range.available_ips.create([{} for i in range(2)]) 61 | # [10.0.0.2/24, 10.0.0.3/24] 62 | ``` 63 | """ 64 | return DetailEndpoint(self, "available-ips", custom_return=IpAddresses) 65 | 66 | 67 | class Prefixes(Record): 68 | def __str__(self): 69 | return str(self.prefix) 70 | 71 | @property 72 | def available_ips(self): 73 | """Represents the ``available-ips`` detail endpoint. 74 | 75 | Returns a DetailEndpoint object that is the interface for 76 | viewing and creating IP addresses inside a prefix. 77 | 78 | ## Returns 79 | DetailEndpoint object. 80 | 81 | ## Examples 82 | 83 | ```python 84 | prefix = nb.ipam.prefixes.get(24) 85 | prefix.available_ips.list() 86 | # [10.0.0.1/24, 10.0.0.2/24, 10.0.0.3/24, 10.0.0.4/24, 10.0.0.5/24, ...] 87 | ``` 88 | 89 | To create a single IP: 90 | 91 | ```python 92 | prefix = nb.ipam.prefixes.get(24) 93 | prefix.available_ips.create() 94 | # 10.0.0.1/24 95 | ``` 96 | 97 | To create multiple IPs: 98 | 99 | ```python 100 | prefix = nb.ipam.prefixes.get(24) 101 | create = prefix.available_ips.create([{} for i in range(2)]) 102 | # [10.0.0.2/24, 10.0.0.3/24] 103 | ``` 104 | """ 105 | return DetailEndpoint(self, "available-ips", custom_return=IpAddresses) 106 | 107 | @property 108 | def available_prefixes(self): 109 | """Represents the ``available-prefixes`` detail endpoint. 110 | 111 | Returns a DetailEndpoint object that is the interface for 112 | viewing and creating prefixes inside a parent prefix. 113 | 114 | Very similar to `available_ips`, except that dict (or list of dicts) passed to `.create()` 115 | needs to have a `prefix_length` key/value specified. 116 | 117 | ## Returns 118 | DetailEndpoint object. 119 | 120 | ## Examples 121 | 122 | ```python 123 | prefix = nb.ipam.prefixes.get(3) 124 | prefix 125 | # 10.0.0.0/16 126 | prefix.available_prefixes.list() 127 | # [10.0.1.0/24, 10.0.2.0/23, 10.0.4.0/22, 10.0.8.0/21, 10.0.16.0/20, 10.0.32.0/19, 10.0.64.0/18, 10.0.128.0/17] 128 | ``` 129 | 130 | Creating a single child prefix: 131 | 132 | ```python 133 | prefix = nb.ipam.prefixes.get(1) 134 | prefix 135 | # 10.0.0.0/24 136 | new_prefix = prefix.available_prefixes.create( 137 | {"prefix_length": 29} 138 | ) 139 | # 10.0.0.16/29 140 | ``` 141 | """ 142 | return DetailEndpoint(self, "available-prefixes", custom_return=Prefixes) 143 | 144 | 145 | class Aggregates(Record): 146 | def __str__(self): 147 | return str(self.prefix) 148 | 149 | 150 | class Vlans(Record): 151 | def __str__(self): 152 | return super().__str__() or str(self.vid) 153 | 154 | 155 | class VlanGroups(Record): 156 | @property 157 | def available_vlans(self): 158 | """Represents the ``available-vlans`` detail endpoint. 159 | 160 | Returns a DetailEndpoint object that is the interface for 161 | viewing and creating VLANs inside a VLAN group. 162 | 163 | ## Returns 164 | DetailEndpoint object. 165 | 166 | ## Examples 167 | 168 | ```python 169 | vlan_group = nb.ipam.vlan_groups.get(1) 170 | vlan_group.available_vlans.list() 171 | # [10, 11, 12] 172 | ``` 173 | 174 | To create a new VLAN: 175 | 176 | ```python 177 | vlan_group.available_vlans.create({"name": "NewVLAN"}) 178 | # NewVLAN (10) 179 | ``` 180 | """ 181 | return DetailEndpoint(self, "available-vlans", custom_return=Vlans) 182 | 183 | 184 | class AsnRanges(Record): 185 | @property 186 | def available_asns(self): 187 | """Represents the ``available-asns`` detail endpoint. 188 | 189 | Returns a DetailEndpoint object that is the interface for 190 | viewing and creating ASNs inside an ASN range. 191 | 192 | ## Returns 193 | DetailEndpoint object. 194 | 195 | ## Examples 196 | 197 | ```python 198 | asn_range = nb.ipam.asn_ranges.get(1) 199 | asn_range.available_asns.list() 200 | # [64512, 64513, 64514] 201 | ``` 202 | 203 | To create a new ASN: 204 | 205 | ```python 206 | asn_range.available_asns.create() 207 | # 64512 208 | ``` 209 | 210 | To create multiple ASNs: 211 | 212 | ```python 213 | asn_range.available_asns.create([{} for i in range(2)]) 214 | # [64513, 64514] 215 | ``` 216 | """ 217 | return DetailEndpoint(self, "available-asns") 218 | -------------------------------------------------------------------------------- /pynetbox/models/mapper.py: -------------------------------------------------------------------------------- 1 | from .circuits import Circuits, CircuitTerminations 2 | from .dcim import ( 3 | Cables, 4 | ConsolePorts, 5 | ConsoleServerPorts, 6 | Devices, 7 | DeviceTypes, 8 | FrontPorts, 9 | Interfaces, 10 | PowerFeeds, 11 | PowerOutlets, 12 | PowerPorts, 13 | RackReservations, 14 | Racks, 15 | RearPorts, 16 | Termination, 17 | VirtualChassis, 18 | ) 19 | from .ipam import Aggregates, IpAddresses, Prefixes, VlanGroups, Vlans 20 | from .virtualization import VirtualMachines 21 | from .wireless import WirelessLans 22 | 23 | CONTENT_TYPE_MAPPER = { 24 | "circuits.circuit": Circuits, 25 | "circuits.circuittermination": CircuitTerminations, 26 | "dcim.cable": Cables, 27 | "dcim.cablepath": None, 28 | "dcim.cabletermination": Termination, 29 | "dcim.consoleport": ConsolePorts, 30 | "dcim.consoleporttemplate": None, 31 | "dcim.consoleserverport": ConsoleServerPorts, 32 | "dcim.consoleserverporttemplate": None, 33 | "dcim.device": Devices, 34 | "dcim.devicebay": None, 35 | "dcim.devicebaytemplate": None, 36 | "dcim.devicerole": None, 37 | "dcim.devicetype": DeviceTypes, 38 | "dcim.frontport": FrontPorts, 39 | "dcim.frontporttemplate": None, 40 | "dcim.interface": Interfaces, 41 | "dcim.interfacetemplate": None, 42 | "dcim.inventoryitem": None, 43 | "dcim.inventoryitemrole": None, 44 | "dcim.inventoryitemtemplate": None, 45 | "dcim.location": None, 46 | "dcim.manufacturer": None, 47 | "dcim.module": None, 48 | "dcim.modulebay": None, 49 | "dcim.modulebaytemplate": None, 50 | "dcim.moduletype": None, 51 | "dcim.platform": None, 52 | "dcim.powerfeed": PowerFeeds, 53 | "dcim.poweroutlet": PowerOutlets, 54 | "dcim.poweroutlettemplate": None, 55 | "dcim.powerpanel": None, 56 | "dcim.powerport": PowerPorts, 57 | "dcim.powerporttemplate": None, 58 | "dcim.rack": Racks, 59 | "dcim.rackreservation": RackReservations, 60 | "dcim.rackrole": None, 61 | "dcim.rearport": RearPorts, 62 | "dcim.rearporttemplate": None, 63 | "dcim.region": None, 64 | "dcim.site": None, 65 | "dcim.sitegroup": None, 66 | "dcim.virtualchassis": VirtualChassis, 67 | "extras.configcontext": None, 68 | "extras.configrevision": None, 69 | "extras.customfield": None, 70 | "extras.customlink": None, 71 | "extras.exporttemplate": None, 72 | "extras.imageattachment": None, 73 | "extras.jobresult": None, 74 | "extras.journalentry": None, 75 | "extras.objectchange": None, 76 | "extras.report": None, 77 | "extras.script": None, 78 | "extras.tag": None, 79 | "extras.taggeditem": None, 80 | "extras.webhook": None, 81 | "ipam.aggregate": Aggregates, 82 | "ipam.ASN": None, 83 | "ipam.FHRPgroup": None, 84 | "ipam.FHRPgroupassignment": None, 85 | "ipam.IPaddress": IpAddresses, 86 | "ipam.IPrange": None, 87 | "ipam.L2VPN": None, 88 | "ipam.L2VPNtermination": None, 89 | "ipam.prefix": Prefixes, 90 | "ipam.RIR": None, 91 | "ipam.role": None, 92 | "ipam.routetarget": None, 93 | "ipam.service": None, 94 | "ipam.servicetemplate": None, 95 | "ipam.VLAN": Vlans, 96 | "ipam.VLANgroup": VlanGroups, 97 | "ipam.VRF": None, 98 | "tenancy.contact": None, 99 | "tenancy.contactassignment": None, 100 | "tenancy.contactgroup": None, 101 | "tenancy.contactrole": None, 102 | "tenancy.tenant": None, 103 | "tenancy.tenantgroup": None, 104 | "virtualization.cluster": None, 105 | "virtualization.clustergroup": None, 106 | "virtualization.clustertype": None, 107 | "virtualization.interface": None, 108 | "virtualization.virtualmachine": VirtualMachines, 109 | "wireless.WirelessLAN": WirelessLans, 110 | "wireless.WirelessLANGroup": None, 111 | "wireless.wirelesslink": None, 112 | } 113 | -------------------------------------------------------------------------------- /pynetbox/models/users.py: -------------------------------------------------------------------------------- 1 | """ 2 | (c) 2017 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from pynetbox.core.response import JsonField, Record 18 | 19 | 20 | class Users(Record): 21 | def __str__(self): 22 | return self.username 23 | 24 | 25 | class Permissions(Record): 26 | users = [Users] 27 | constraints = JsonField 28 | -------------------------------------------------------------------------------- /pynetbox/models/virtualization.py: -------------------------------------------------------------------------------- 1 | """ 2 | (c) 2017 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from pynetbox.core.response import JsonField, Record 18 | from pynetbox.models.ipam import IpAddresses 19 | 20 | 21 | class VirtualMachines(Record): 22 | primary_ip = IpAddresses 23 | primary_ip4 = IpAddresses 24 | primary_ip6 = IpAddresses 25 | config_context = JsonField 26 | -------------------------------------------------------------------------------- /pynetbox/models/wireless.py: -------------------------------------------------------------------------------- 1 | """ 2 | (c) 2017 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from pynetbox.core.response import Record 18 | 19 | 20 | class WirelessLans(Record): 21 | def __str__(self): 22 | return self.ssid 23 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | black 2 | pytest 3 | pytest-docker 4 | PyYAML 5 | mkdocs-material==9.6.14 6 | mkdocstrings[python]==0.29.1 7 | pymdown-extensions>=10.0 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.32.3,<3.0 2 | urllib3>=2.2.3,<3 3 | packaging 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup( 4 | name="pynetbox", 5 | description="NetBox API client library", 6 | url="https://github.com/netbox-community/pynetbox", 7 | author="Zach Moody, Arthur Hanson", 8 | author_email="ahanson@netboxlabs.com", 9 | license="Apache2", 10 | include_package_data=True, 11 | use_scm_version=True, 12 | setup_requires=["setuptools_scm"], 13 | packages=find_packages(exclude=["tests", "tests.*"]), 14 | long_description=open("README.md").read(), 15 | long_description_content_type="text/markdown", 16 | install_requires=["requests>=2.20.0,<3.0", "packaging"], 17 | zip_safe=False, 18 | keywords=["netbox"], 19 | classifiers=[ 20 | "Intended Audience :: Developers", 21 | "Development Status :: 5 - Production/Stable", 22 | "Programming Language :: Python :: 3", 23 | "Programming Language :: Python :: 3.10", 24 | "Programming Language :: Python :: 3.11", 25 | "Programming Language :: Python :: 3.12", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/pynetbox/c159a765fe8c56cdf3a1e276357ebd60867b0cea/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from urllib import parse 2 | 3 | import pytest 4 | from packaging import version 5 | 6 | DEFAULT_NETBOX_VERSIONS = "4.3" 7 | 8 | 9 | def pytest_addoption(parser): 10 | """Hook on the pytest option parser setup. 11 | 12 | Add some extra options to the parser. 13 | """ 14 | parser.addoption( 15 | "--netbox-versions", 16 | action="store", 17 | default=DEFAULT_NETBOX_VERSIONS, 18 | help=( 19 | "The versions of netbox to run integration tests against, as a" 20 | " comma-separated list. Default: %s" % DEFAULT_NETBOX_VERSIONS 21 | ), 22 | ) 23 | 24 | parser.addoption( 25 | "--no-cleanup", 26 | dest="cleanup", 27 | action="store_false", 28 | help=( 29 | "Skip any cleanup steps after the pytest session finishes. Any containers" 30 | " created will be left running and the docker-compose files used to" 31 | " create them will be left on disk." 32 | ), 33 | ) 34 | 35 | parser.addoption( 36 | "--url-override", 37 | dest="url_override", 38 | action="store", 39 | help=( 40 | "Overrides the URL to run tests to. This allows for testing to the same" 41 | " containers for separate runs." 42 | ), 43 | ) 44 | 45 | 46 | def pytest_configure(config): 47 | """Hook that runs after test collection is completed. 48 | 49 | Here we can modify items in the collected tests or parser args. 50 | """ 51 | # verify the netbox versions parse correctly and split them 52 | config.option.netbox_versions = [ 53 | version.Version(version_string) 54 | for version_string in config.option.netbox_versions.split(",") 55 | ] 56 | if "no:docker" in config.option.plugins and config.option.url_override: 57 | url_parse = parse.urlparse(config.option.url_override) 58 | 59 | class DockerServicesMock: 60 | def __init__(self, ports): 61 | self.ports = ports 62 | 63 | def wait_until_responsive(self, *args, **kwargs): 64 | return None 65 | 66 | def port_for(self, *args): 67 | return self.ports 68 | 69 | class Plugin: 70 | @pytest.fixture(scope="session") 71 | def docker_ip(self): 72 | return "127.0.0.1" 73 | 74 | @pytest.fixture(scope="session") 75 | def docker_services(self): 76 | return DockerServicesMock(url_parse.port) 77 | 78 | config.pluginmanager.register(Plugin()) 79 | -------------------------------------------------------------------------------- /tests/fixtures/api/token_provision.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "1234567890123456789012345678901234567890" 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/circuits/circuit.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "cid": "123456", 4 | "provider": { 5 | "id": 1, 6 | "url": "http://localhost:8000/api/circuits/providers/1/", 7 | "name": "TEST", 8 | "slug": "test" 9 | }, 10 | "type": { 11 | "id": 1, 12 | "url": "http://localhost:8000/api/circuits/circuit-types/1/", 13 | "name": "Transit", 14 | "slug": "transit" 15 | }, 16 | "tenant": null, 17 | "install_date": null, 18 | "commit_rate": null, 19 | "description": "", 20 | "comments": "", 21 | "custom_fields": {} 22 | } -------------------------------------------------------------------------------- /tests/fixtures/circuits/circuit_termination.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "circuit": { 4 | "id": 1, 5 | "url": "http://localhost:8000/api/circuits/circuits/1/", 6 | "cid": "123456" 7 | }, 8 | "term_side": "A", 9 | "site": { 10 | "id": 1, 11 | "url": "http://localhost:8000/api/dcim/sites/1/", 12 | "name": "TEST1", 13 | "slug": "test1" 14 | }, 15 | "interface": { 16 | "id": 4, 17 | "device": { 18 | "id": 1, 19 | "url": "http://localhost:8000/api/dcim/devices/1/", 20 | "name": "test1-edge1", 21 | "display_name": "test1-edge1" 22 | }, 23 | "name": "xe-0/0/0", 24 | "form_factor": { 25 | "value": 1200, 26 | "label": "SFP+ (10GE)" 27 | }, 28 | "lag": null, 29 | "mac_address": null, 30 | "mgmt_only": false, 31 | "description": "TEST", 32 | "connection": null, 33 | "connected_interface": null 34 | }, 35 | "port_speed": 1000000, 36 | "upstream_speed": null, 37 | "xconnect_id": "", 38 | "pp_info": "" 39 | } -------------------------------------------------------------------------------- /tests/fixtures/circuits/circuit_terminations.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "circuit": { 9 | "id": 1, 10 | "url": "http://localhost:8000/api/circuits/circuits/1/", 11 | "cid": "123456" 12 | }, 13 | "term_side": "A", 14 | "site": { 15 | "id": 1, 16 | "url": "http://localhost:8000/api/dcim/sites/1/", 17 | "name": "TEST1", 18 | "slug": "test1" 19 | }, 20 | "interface": { 21 | "id": 4, 22 | "device": { 23 | "id": 1, 24 | "url": "http://localhost:8000/api/dcim/devices/1/", 25 | "name": "test1-edge1", 26 | "display_name": "test1-edge1" 27 | }, 28 | "name": "xe-0/0/0", 29 | "form_factor": { 30 | "value": 1200, 31 | "label": "SFP+ (10GE)" 32 | }, 33 | "lag": null, 34 | "mac_address": null, 35 | "mgmt_only": false, 36 | "description": "TEST", 37 | "connection": null, 38 | "connected_interface": null 39 | }, 40 | "port_speed": 1000000, 41 | "upstream_speed": null, 42 | "xconnect_id": "", 43 | "pp_info": "" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /tests/fixtures/circuits/circuit_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Transit", 4 | "slug": "transit" 5 | } -------------------------------------------------------------------------------- /tests/fixtures/circuits/circuit_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "Transit", 9 | "slug": "transit" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/fixtures/circuits/circuits.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "cid": "123456", 9 | "provider": { 10 | "id": 1, 11 | "url": "http://localhost:8000/api/circuits/providers/1/", 12 | "name": "TEST", 13 | "slug": "test" 14 | }, 15 | "type": { 16 | "id": 1, 17 | "url": "http://localhost:8000/api/circuits/circuit-types/1/", 18 | "name": "Transit", 19 | "slug": "transit" 20 | }, 21 | "tenant": null, 22 | "install_date": null, 23 | "commit_rate": null, 24 | "description": "", 25 | "comments": "", 26 | "custom_fields": {} 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /tests/fixtures/circuits/provider.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "TEST", 4 | "slug": "test", 5 | "asn": null, 6 | "account": "", 7 | "portal_url": "", 8 | "noc_contact": "", 9 | "admin_contact": "", 10 | "comments": "", 11 | "custom_fields": {} 12 | } -------------------------------------------------------------------------------- /tests/fixtures/circuits/providers.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "TEST", 9 | "slug": "test", 10 | "asn": null, 11 | "account": "", 12 | "portal_url": "", 13 | "noc_contact": "", 14 | "admin_contact": "", 15 | "comments": "", 16 | "custom_fields": {} 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/cable.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "termination_a_type": "dcim.consoleport", 4 | "termination_a_id": 1, 5 | "termination_a": { 6 | "id": 1, 7 | "url": "http://localhost:8000/api/dcim/console-ports/1/", 8 | "device": { 9 | "id": 1, 10 | "url": "http://localhost:8000/api/dcim/devices/1/", 11 | "name": "tst1-test1", 12 | "display_name": "tst1-test1" 13 | }, 14 | "name": "Console", 15 | "cable": 1 16 | }, 17 | "termination_b_type": "dcim.consoleserverport", 18 | "termination_b_id": 2, 19 | "termination_b": { 20 | "id": 2, 21 | "url": "http://localhost:8000/api/dcim/console-server-ports/2/", 22 | "device": { 23 | "id": 2, 24 | "url": "http://localhost:8000/api/dcim/devices/2/", 25 | "name": "tst1-test2", 26 | "display_name": "tst1-test2" 27 | }, 28 | "name": "Port 10", 29 | "cable": 1 30 | }, 31 | "type": null, 32 | "status": { 33 | "value": true, 34 | "label": "Connected" 35 | }, 36 | "label": "", 37 | "color": "", 38 | "length": null, 39 | "length_unit": null 40 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/cables.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 3, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "termination_a_type": "dcim.consoleport", 9 | "termination_a_id": 1, 10 | "termination_a": { 11 | "id": 1, 12 | "url": "http://localhost:8000/api/dcim/console-ports/1/", 13 | "device": { 14 | "id": 1, 15 | "url": "http://localhost:8000/api/dcim/devices/1/", 16 | "name": "tst1-test1", 17 | "display_name": "tst1-test1" 18 | }, 19 | "name": "Console", 20 | "cable": 1 21 | }, 22 | "termination_b_type": "dcim.consoleserverport", 23 | "termination_b_id": 2, 24 | "termination_b": { 25 | "id": 2, 26 | "url": "http://localhost:8000/api/dcim/console-server-ports/2/", 27 | "device": { 28 | "id": 2, 29 | "url": "http://localhost:8000/api/dcim/devices/2/", 30 | "name": "tst1-test2", 31 | "display_name": "tst1-test2" 32 | }, 33 | "name": "Port 10", 34 | "cable": 1 35 | }, 36 | "type": null, 37 | "status": { 38 | "value": true, 39 | "label": "Connected" 40 | }, 41 | "label": "", 42 | "color": "", 43 | "length": null, 44 | "length_unit": null 45 | }, 46 | { 47 | "id": 2, 48 | "termination_a_type": "dcim.consoleport", 49 | "termination_a_id": 3, 50 | "termination_a": { 51 | "id": 3, 52 | "url": "http://localhost:8000/api/dcim/console-ports/3/", 53 | "device": { 54 | "id": 3, 55 | "url": "http://localhost:8000/api/dcim/devices/3/", 56 | "name": "tst1-test3", 57 | "display_name": "tst1-test3" 58 | }, 59 | "name": "Console", 60 | "cable": 2 61 | }, 62 | "termination_b_type": "dcim.consoleserverport", 63 | "termination_b_id": 4, 64 | "termination_b": { 65 | "id": 4, 66 | "url": "http://localhost:8000/api/dcim/console-server-ports/4/", 67 | "device": { 68 | "id": 4, 69 | "url": "http://localhost:8000/api/dcim/devices/4/", 70 | "name": "tst1-test4", 71 | "display_name": "tst1-test4" 72 | }, 73 | "name": "Port 11", 74 | "cable": 2 75 | }, 76 | "type": null, 77 | "status": { 78 | "value": true, 79 | "label": "Connected" 80 | }, 81 | "label": "", 82 | "color": "", 83 | "length": null, 84 | "length_unit": null 85 | }, 86 | { 87 | "id": 3, 88 | "termination_a_type": "dcim.consoleport", 89 | "termination_a_id": 5, 90 | "termination_a": { 91 | "id": 5, 92 | "url": "http://localhost:8000/api/dcim/console-ports/5/", 93 | "device": { 94 | "id": 5, 95 | "url": "http://localhost:8000/api/dcim/devices/5/", 96 | "name": "tst1-test5", 97 | "display_name": "tst1-test5" 98 | }, 99 | "name": "Console", 100 | "cable": 3 101 | }, 102 | "termination_b_type": "dcim.consoleserverport", 103 | "termination_b_id": 6, 104 | "termination_b": { 105 | "id": 6, 106 | "url": "http://localhost:8000/api/dcim/console-server-ports/6/", 107 | "device": { 108 | "id": 6, 109 | "url": "http://localhost:8000/api/dcim/devices/6/", 110 | "name": "tst1-test6", 111 | "display_name": "tst1-test6" 112 | }, 113 | "name": "Port 1", 114 | "cable": 3 115 | }, 116 | "type": null, 117 | "status": { 118 | "value": true, 119 | "label": "Connected" 120 | }, 121 | "label": "", 122 | "color": "", 123 | "length": null, 124 | "length_unit": null 125 | } 126 | ] 127 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/console_port.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device": { 4 | "id": 1, 5 | "url": "http://localhost:8000/api/dcim/devices/1/", 6 | "name": "test1-edge1", 7 | "display_name": "test1-edge1" 8 | }, 9 | "name": "Console (RE0)", 10 | "cs_port": { 11 | "id": 27, 12 | "device": { 13 | "id": 9, 14 | "url": "http://localhost:8000/api/dcim/devices/9/", 15 | "name": "test1-oob1", 16 | "display_name": "test1-oob1" 17 | }, 18 | "name": "Port 3", 19 | "connected_console": 1 20 | }, 21 | "connection_status": true 22 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/console_port_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device_type": { 4 | "id": 1, 5 | "url": "http://localhost:8000/api/dcim/device-types/1/", 6 | "manufacturer": { 7 | "id": 1, 8 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 9 | "name": "Juniper", 10 | "slug": "juniper" 11 | }, 12 | "model": "MX960", 13 | "slug": "mx960" 14 | }, 15 | "name": "Console (RE0)" 16 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/console_port_templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 7, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 3, 8 | "device_type": { 9 | "id": 2, 10 | "url": "http://localhost:8000/api/dcim/device-types/2/", 11 | "manufacturer": { 12 | "id": 1, 13 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 14 | "name": "Juniper", 15 | "slug": "juniper" 16 | }, 17 | "model": "EX9214", 18 | "slug": "ex9214" 19 | }, 20 | "name": "Console (RE0)" 21 | }, 22 | { 23 | "id": 4, 24 | "device_type": { 25 | "id": 2, 26 | "url": "http://localhost:8000/api/dcim/device-types/2/", 27 | "manufacturer": { 28 | "id": 1, 29 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 30 | "name": "Juniper", 31 | "slug": "juniper" 32 | }, 33 | "model": "EX9214", 34 | "slug": "ex9214" 35 | }, 36 | "name": "Console (RE1)" 37 | }, 38 | { 39 | "id": 1, 40 | "device_type": { 41 | "id": 1, 42 | "url": "http://localhost:8000/api/dcim/device-types/1/", 43 | "manufacturer": { 44 | "id": 1, 45 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 46 | "name": "Juniper", 47 | "slug": "juniper" 48 | }, 49 | "model": "MX960", 50 | "slug": "mx960" 51 | }, 52 | "name": "Console (RE0)" 53 | }, 54 | { 55 | "id": 2, 56 | "device_type": { 57 | "id": 1, 58 | "url": "http://localhost:8000/api/dcim/device-types/1/", 59 | "manufacturer": { 60 | "id": 1, 61 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 62 | "name": "Juniper", 63 | "slug": "juniper" 64 | }, 65 | "model": "MX960", 66 | "slug": "mx960" 67 | }, 68 | "name": "Console (RE1)" 69 | }, 70 | { 71 | "id": 5, 72 | "device_type": { 73 | "id": 3, 74 | "url": "http://localhost:8000/api/dcim/device-types/3/", 75 | "manufacturer": { 76 | "id": 1, 77 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 78 | "name": "Juniper", 79 | "slug": "juniper" 80 | }, 81 | "model": "QFX5100-24Q", 82 | "slug": "qfx5100-24q" 83 | }, 84 | "name": "Console" 85 | }, 86 | { 87 | "id": 6, 88 | "device_type": { 89 | "id": 5, 90 | "url": "http://localhost:8000/api/dcim/device-types/5/", 91 | "manufacturer": { 92 | "id": 2, 93 | "url": "http://localhost:8000/api/dcim/manufacturers/2/", 94 | "name": "Opengear", 95 | "slug": "opengear" 96 | }, 97 | "model": "CM4148", 98 | "slug": "cm4148" 99 | }, 100 | "name": "Console" 101 | }, 102 | { 103 | "id": 7, 104 | "device_type": { 105 | "id": 6, 106 | "url": "http://localhost:8000/api/dcim/device-types/6/", 107 | "manufacturer": { 108 | "id": 3, 109 | "url": "http://localhost:8000/api/dcim/manufacturers/3/", 110 | "name": "ServerTech", 111 | "slug": "servertech" 112 | }, 113 | "model": "CWG-24VYM415C9", 114 | "slug": "cwg-24vym415c9" 115 | }, 116 | "name": "Serial" 117 | } 118 | ] 119 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/console_ports.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 15, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 3, 8 | "device": { 9 | "id": 2, 10 | "url": "http://localhost:8000/api/dcim/devices/2/", 11 | "name": "test1-core1", 12 | "display_name": "test1-core1" 13 | }, 14 | "name": "Console (RE0)", 15 | "cs_port": { 16 | "id": 5, 17 | "device": { 18 | "id": 9, 19 | "url": "http://localhost:8000/api/dcim/devices/9/", 20 | "name": "test1-oob1", 21 | "display_name": "test1-oob1" 22 | }, 23 | "name": "Port 1", 24 | "connected_console": 3 25 | }, 26 | "connection_status": true 27 | }, 28 | { 29 | "id": 4, 30 | "device": { 31 | "id": 2, 32 | "url": "http://localhost:8000/api/dcim/devices/2/", 33 | "name": "test1-core1", 34 | "display_name": "test1-core1" 35 | }, 36 | "name": "Console (RE1)", 37 | "cs_port": { 38 | "id": 16, 39 | "device": { 40 | "id": 9, 41 | "url": "http://localhost:8000/api/dcim/devices/9/", 42 | "name": "test1-oob1", 43 | "display_name": "test1-oob1" 44 | }, 45 | "name": "Port 2", 46 | "connected_console": 4 47 | }, 48 | "connection_status": true 49 | }, 50 | { 51 | "id": 11, 52 | "device": { 53 | "id": 8, 54 | "url": "http://localhost:8000/api/dcim/devices/8/", 55 | "name": "test1-core2", 56 | "display_name": "test1-core2" 57 | }, 58 | "name": "Console (RE0)", 59 | "cs_port": null, 60 | "connection_status": true 61 | }, 62 | { 63 | "id": 12, 64 | "device": { 65 | "id": 8, 66 | "url": "http://localhost:8000/api/dcim/devices/8/", 67 | "name": "test1-core2", 68 | "display_name": "test1-core2" 69 | }, 70 | "name": "Console (RE1)", 71 | "cs_port": null, 72 | "connection_status": true 73 | }, 74 | { 75 | "id": 1, 76 | "device": { 77 | "id": 1, 78 | "url": "http://localhost:8000/api/dcim/devices/1/", 79 | "name": "test1-edge1", 80 | "display_name": "test1-edge1" 81 | }, 82 | "name": "Console (RE0)", 83 | "cs_port": { 84 | "id": 27, 85 | "device": { 86 | "id": 9, 87 | "url": "http://localhost:8000/api/dcim/devices/9/", 88 | "name": "test1-oob1", 89 | "display_name": "test1-oob1" 90 | }, 91 | "name": "Port 3", 92 | "connected_console": 1 93 | }, 94 | "connection_status": true 95 | }, 96 | { 97 | "id": 2, 98 | "device": { 99 | "id": 1, 100 | "url": "http://localhost:8000/api/dcim/devices/1/", 101 | "name": "test1-edge1", 102 | "display_name": "test1-edge1" 103 | }, 104 | "name": "Console (RE1)", 105 | "cs_port": { 106 | "id": 38, 107 | "device": { 108 | "id": 9, 109 | "url": "http://localhost:8000/api/dcim/devices/9/", 110 | "name": "test1-oob1", 111 | "display_name": "test1-oob1" 112 | }, 113 | "name": "Port 4", 114 | "connected_console": 2 115 | }, 116 | "connection_status": true 117 | }, 118 | { 119 | "id": 9, 120 | "device": { 121 | "id": 7, 122 | "url": "http://localhost:8000/api/dcim/devices/7/", 123 | "name": "test1-edge2", 124 | "display_name": "test1-edge2" 125 | }, 126 | "name": "Console (RE0)", 127 | "cs_port": null, 128 | "connection_status": true 129 | }, 130 | { 131 | "id": 10, 132 | "device": { 133 | "id": 7, 134 | "url": "http://localhost:8000/api/dcim/devices/7/", 135 | "name": "test1-edge2", 136 | "display_name": "test1-edge2" 137 | }, 138 | "name": "Console (RE1)", 139 | "cs_port": null, 140 | "connection_status": true 141 | }, 142 | { 143 | "id": 6, 144 | "device": { 145 | "id": 4, 146 | "url": "http://localhost:8000/api/dcim/devices/4/", 147 | "name": "test1-leaf1", 148 | "display_name": "test1-leaf1" 149 | }, 150 | "name": "Console", 151 | "cs_port": { 152 | "id": 48, 153 | "device": { 154 | "id": 9, 155 | "url": "http://localhost:8000/api/dcim/devices/9/", 156 | "name": "test1-oob1", 157 | "display_name": "test1-oob1" 158 | }, 159 | "name": "Port 5", 160 | "connected_console": 6 161 | }, 162 | "connection_status": true 163 | }, 164 | { 165 | "id": 7, 166 | "device": { 167 | "id": 5, 168 | "url": "http://localhost:8000/api/dcim/devices/5/", 169 | "name": "test1-leaf2", 170 | "display_name": "test1-leaf2" 171 | }, 172 | "name": "Console", 173 | "cs_port": null, 174 | "connection_status": true 175 | }, 176 | { 177 | "id": 13, 178 | "device": { 179 | "id": 9, 180 | "url": "http://localhost:8000/api/dcim/devices/9/", 181 | "name": "test1-oob1", 182 | "display_name": "test1-oob1" 183 | }, 184 | "name": "Console", 185 | "cs_port": null, 186 | "connection_status": true 187 | }, 188 | { 189 | "id": 15, 190 | "device": { 191 | "id": 11, 192 | "url": "http://localhost:8000/api/dcim/devices/11/", 193 | "name": "test1-pdu1", 194 | "display_name": "test1-pdu1" 195 | }, 196 | "name": "Serial", 197 | "cs_port": null, 198 | "connection_status": true 199 | }, 200 | { 201 | "id": 16, 202 | "device": { 203 | "id": 12, 204 | "url": "http://localhost:8000/api/dcim/devices/12/", 205 | "name": "test1-pdu2", 206 | "display_name": "test1-pdu2" 207 | }, 208 | "name": "Serial", 209 | "cs_port": null, 210 | "connection_status": true 211 | }, 212 | { 213 | "id": 5, 214 | "device": { 215 | "id": 3, 216 | "url": "http://localhost:8000/api/dcim/devices/3/", 217 | "name": "test1-spine1", 218 | "display_name": "test1-spine1" 219 | }, 220 | "name": "Console", 221 | "cs_port": { 222 | "id": 49, 223 | "device": { 224 | "id": 9, 225 | "url": "http://localhost:8000/api/dcim/devices/9/", 226 | "name": "test1-oob1", 227 | "display_name": "test1-oob1" 228 | }, 229 | "name": "Port 6", 230 | "connected_console": 5 231 | }, 232 | "connection_status": true 233 | }, 234 | { 235 | "id": 8, 236 | "device": { 237 | "id": 6, 238 | "url": "http://localhost:8000/api/dcim/devices/6/", 239 | "name": "test1-spine2", 240 | "display_name": "test1-spine2" 241 | }, 242 | "name": "Console", 243 | "cs_port": null, 244 | "connection_status": true 245 | } 246 | ] 247 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/console_server_port.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device": { 4 | "id": 9, 5 | "url": "http://localhost:8000/api/dcim/devices/9/", 6 | "name": "test1-oob1", 7 | "display_name": "test1-oob1" 8 | }, 9 | "name": "Port 1", 10 | "connected_console": 3 11 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/console_server_port_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device_type": { 4 | "id": 3, 5 | "url": "http://localhost:8000/api/dcim/device-types/3/", 6 | "manufacturer": { 7 | "id": 1, 8 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 9 | "name": "Juniper", 10 | "slug": "juniper" 11 | }, 12 | "model": "QFX5100-24Q", 13 | "slug": "qfx5100-24q" 14 | }, 15 | "name": "Console" 16 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/device.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "test1-edge1", 4 | "display_name": "test1-edge1", 5 | "device_type": { 6 | "id": 1, 7 | "url": "http://localhost:8000/api/dcim/device-types/1/", 8 | "manufacturer": { 9 | "id": 1, 10 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 11 | "name": "Juniper", 12 | "slug": "juniper" 13 | }, 14 | "model": "MX960", 15 | "slug": "mx960" 16 | }, 17 | "role": { 18 | "id": 1, 19 | "url": "http://localhost:8000/api/dcim/device-roles/1/", 20 | "name": "Router", 21 | "slug": "router" 22 | }, 23 | "tenant": null, 24 | "platform": { 25 | "id": 1, 26 | "url": "http://localhost:8000/api/dcim/platforms/1/", 27 | "name": "Juniper Junos", 28 | "slug": "juniper-junos" 29 | }, 30 | "serial": "5555555555", 31 | "asset_tag": null, 32 | "site": { 33 | "id": 1, 34 | "url": "http://localhost:8000/api/dcim/sites/1/", 35 | "name": "TEST1", 36 | "slug": "test1" 37 | }, 38 | "rack": { 39 | "id": 1, 40 | "url": "http://localhost:8000/api/dcim/racks/1/", 41 | "name": "A1R1", 42 | "display_name": "A1R1 (T23A01)" 43 | }, 44 | "position": 1, 45 | "face": { 46 | "value": 0, 47 | "label": "Front" 48 | }, 49 | "parent_device": null, 50 | "status": { 51 | "value": true, 52 | "label": "Active" 53 | }, 54 | "primary_ip": { 55 | "id": 1, 56 | "url": "http://localhost:8000/api/ipam/ip-addresses/1/", 57 | "family": 4, 58 | "address": "10.0.255.1/32" 59 | }, 60 | "primary_ip4": { 61 | "id": 1, 62 | "url": "http://localhost:8000/api/ipam/ip-addresses/1/", 63 | "family": 4, 64 | "address": "10.0.255.1/32" 65 | }, 66 | "primary_ip6": null, 67 | "comments": "", 68 | "local_context_data": { 69 | "testing": "test" 70 | }, 71 | "custom_fields": {}, 72 | "config_context": { 73 | "test_key": "test_val" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/fixtures/dcim/device_bay.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device": { 4 | "id": 13, 5 | "url": "http://localhost:8000/api/dcim/devices/13/", 6 | "name": "test1-mothership", 7 | "display_name": "test1-mothership" 8 | }, 9 | "name": "thing-1", 10 | "installed_device": null 11 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/device_bay_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device_type": { 4 | "id": 7, 5 | "url": "http://localhost:8000/api/dcim/device-types/7/", 6 | "manufacturer": { 7 | "id": 3, 8 | "url": "http://localhost:8000/api/dcim/manufacturers/3/", 9 | "name": "ServerTech", 10 | "slug": "servertech" 11 | }, 12 | "model": "Mothership", 13 | "slug": "mothership" 14 | }, 15 | "name": "thing-1" 16 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/device_bay_templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 3, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "device_type": { 9 | "id": 7, 10 | "url": "http://localhost:8000/api/dcim/device-types/7/", 11 | "manufacturer": { 12 | "id": 3, 13 | "url": "http://localhost:8000/api/dcim/manufacturers/3/", 14 | "name": "ServerTech", 15 | "slug": "servertech" 16 | }, 17 | "model": "Mothership", 18 | "slug": "mothership" 19 | }, 20 | "name": "thing-1" 21 | }, 22 | { 23 | "id": 2, 24 | "device_type": { 25 | "id": 7, 26 | "url": "http://localhost:8000/api/dcim/device-types/7/", 27 | "manufacturer": { 28 | "id": 3, 29 | "url": "http://localhost:8000/api/dcim/manufacturers/3/", 30 | "name": "ServerTech", 31 | "slug": "servertech" 32 | }, 33 | "model": "Mothership", 34 | "slug": "mothership" 35 | }, 36 | "name": "thing-2" 37 | }, 38 | { 39 | "id": 3, 40 | "device_type": { 41 | "id": 7, 42 | "url": "http://localhost:8000/api/dcim/device-types/7/", 43 | "manufacturer": { 44 | "id": 3, 45 | "url": "http://localhost:8000/api/dcim/manufacturers/3/", 46 | "name": "ServerTech", 47 | "slug": "servertech" 48 | }, 49 | "model": "Mothership", 50 | "slug": "mothership" 51 | }, 52 | "name": "thing-3" 53 | } 54 | ] 55 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/device_bays.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 3, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "device": { 9 | "id": 13, 10 | "url": "http://localhost:8000/api/dcim/devices/13/", 11 | "name": "test1-mothership", 12 | "display_name": "test1-mothership" 13 | }, 14 | "name": "thing-1", 15 | "installed_device": null 16 | }, 17 | { 18 | "id": 2, 19 | "device": { 20 | "id": 13, 21 | "url": "http://localhost:8000/api/dcim/devices/13/", 22 | "name": "test1-mothership", 23 | "display_name": "test1-mothership" 24 | }, 25 | "name": "thing-2", 26 | "installed_device": null 27 | }, 28 | { 29 | "id": 3, 30 | "device": { 31 | "id": 13, 32 | "url": "http://localhost:8000/api/dcim/devices/13/", 33 | "name": "test1-mothership", 34 | "display_name": "test1-mothership" 35 | }, 36 | "name": "thing-3", 37 | "installed_device": null 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/device_bulk_create.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "test1-core3", 5 | "device_type": 1, 6 | "role": 3, 7 | "tenant": null, 8 | "platform": null, 9 | "serial": "", 10 | "asset_tag": null, 11 | "site": 1, 12 | "rack": null, 13 | "position": null, 14 | "face": null, 15 | "status": 1, 16 | "primary_ip4": null, 17 | "primary_ip6": null, 18 | "cluster": null, 19 | "comments": "" 20 | }, 21 | { 22 | "id": 2, 23 | "name": "test1-core4", 24 | "device_type": 1, 25 | "role": 3, 26 | "tenant": null, 27 | "platform": null, 28 | "serial": "", 29 | "asset_tag": null, 30 | "site": 1, 31 | "rack": null, 32 | "position": null, 33 | "face": null, 34 | "status": 1, 35 | "primary_ip4": null, 36 | "primary_ip6": null, 37 | "cluster": null, 38 | "comments": "" 39 | } 40 | ] -------------------------------------------------------------------------------- /tests/fixtures/dcim/device_role.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Router", 4 | "slug": "router", 5 | "color": "purple" 6 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/device_roles.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 6, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 3, 8 | "name": "Core Switch", 9 | "slug": "core-switch", 10 | "color": "red" 11 | }, 12 | { 13 | "id": 4, 14 | "name": "Leaf Switch", 15 | "slug": "leaf-switch", 16 | "color": "teal" 17 | }, 18 | { 19 | "id": 5, 20 | "name": "OOB Switch", 21 | "slug": "oob-switch", 22 | "color": "purple" 23 | }, 24 | { 25 | "id": 6, 26 | "name": "PDU", 27 | "slug": "pdu", 28 | "color": "yellow" 29 | }, 30 | { 31 | "id": 1, 32 | "name": "Router", 33 | "slug": "router", 34 | "color": "purple" 35 | }, 36 | { 37 | "id": 2, 38 | "name": "Spine Switch", 39 | "slug": "spine-switch", 40 | "color": "green" 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/device_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "manufacturer": { 4 | "id": 1, 5 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 6 | "name": "Juniper", 7 | "slug": "juniper" 8 | }, 9 | "model": "MX960", 10 | "slug": "mx960", 11 | "part_number": "", 12 | "u_height": 16, 13 | "is_full_depth": true, 14 | "interface_ordering": { 15 | "value": 1, 16 | "label": "Slot/position" 17 | }, 18 | "is_console_server": false, 19 | "is_pdu": false, 20 | "is_network_device": true, 21 | "subdevice_role": null, 22 | "comments": "", 23 | "custom_fields": {}, 24 | "instance_count": 2 25 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/device_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 6, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 2, 8 | "manufacturer": { 9 | "id": 1, 10 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 11 | "name": "Juniper", 12 | "slug": "juniper" 13 | }, 14 | "model": "EX9214", 15 | "slug": "ex9214", 16 | "part_number": "", 17 | "u_height": 16, 18 | "is_full_depth": true, 19 | "interface_ordering": { 20 | "value": 1, 21 | "label": "Slot/position" 22 | }, 23 | "is_console_server": false, 24 | "is_pdu": false, 25 | "is_network_device": true, 26 | "subdevice_role": null, 27 | "comments": "", 28 | "custom_fields": {}, 29 | "instance_count": 2 30 | }, 31 | { 32 | "id": 1, 33 | "manufacturer": { 34 | "id": 1, 35 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 36 | "name": "Juniper", 37 | "slug": "juniper" 38 | }, 39 | "model": "MX960", 40 | "slug": "mx960", 41 | "part_number": "", 42 | "u_height": 16, 43 | "is_full_depth": true, 44 | "interface_ordering": { 45 | "value": 1, 46 | "label": "Slot/position" 47 | }, 48 | "is_console_server": false, 49 | "is_pdu": false, 50 | "is_network_device": true, 51 | "subdevice_role": null, 52 | "comments": "", 53 | "custom_fields": {}, 54 | "instance_count": 2 55 | }, 56 | { 57 | "id": 3, 58 | "manufacturer": { 59 | "id": 1, 60 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 61 | "name": "Juniper", 62 | "slug": "juniper" 63 | }, 64 | "model": "QFX5100-24Q", 65 | "slug": "qfx5100-24q", 66 | "part_number": "", 67 | "u_height": 1, 68 | "is_full_depth": true, 69 | "interface_ordering": { 70 | "value": 1, 71 | "label": "Slot/position" 72 | }, 73 | "is_console_server": false, 74 | "is_pdu": false, 75 | "is_network_device": true, 76 | "subdevice_role": null, 77 | "comments": "", 78 | "custom_fields": {}, 79 | "instance_count": 2 80 | }, 81 | { 82 | "id": 4, 83 | "manufacturer": { 84 | "id": 1, 85 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 86 | "name": "Juniper", 87 | "slug": "juniper" 88 | }, 89 | "model": "QFX5100-48S", 90 | "slug": "qfx5100-48s", 91 | "part_number": "", 92 | "u_height": 1, 93 | "is_full_depth": true, 94 | "interface_ordering": { 95 | "value": 1, 96 | "label": "Slot/position" 97 | }, 98 | "is_console_server": false, 99 | "is_pdu": false, 100 | "is_network_device": true, 101 | "subdevice_role": null, 102 | "comments": "", 103 | "custom_fields": {}, 104 | "instance_count": 2 105 | }, 106 | { 107 | "id": 5, 108 | "manufacturer": { 109 | "id": 2, 110 | "url": "http://localhost:8000/api/dcim/manufacturers/2/", 111 | "name": "Opengear", 112 | "slug": "opengear" 113 | }, 114 | "model": "CM4148", 115 | "slug": "cm4148", 116 | "part_number": "", 117 | "u_height": 1, 118 | "is_full_depth": true, 119 | "interface_ordering": { 120 | "value": 1, 121 | "label": "Slot/position" 122 | }, 123 | "is_console_server": true, 124 | "is_pdu": false, 125 | "is_network_device": false, 126 | "subdevice_role": null, 127 | "comments": "", 128 | "custom_fields": {}, 129 | "instance_count": 1 130 | }, 131 | { 132 | "id": 6, 133 | "manufacturer": { 134 | "id": 3, 135 | "url": "http://localhost:8000/api/dcim/manufacturers/3/", 136 | "name": "ServerTech", 137 | "slug": "servertech" 138 | }, 139 | "model": "CWG-24VYM415C9", 140 | "slug": "cwg-24vym415c9", 141 | "part_number": "", 142 | "u_height": 0, 143 | "is_full_depth": false, 144 | "interface_ordering": { 145 | "value": 1, 146 | "label": "Slot/position" 147 | }, 148 | "is_console_server": false, 149 | "is_pdu": true, 150 | "is_network_device": false, 151 | "subdevice_role": null, 152 | "comments": "", 153 | "custom_fields": {}, 154 | "instance_count": 2 155 | } 156 | ] 157 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/interface.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device": { 4 | "id": 2, 5 | "url": "http://localhost:8000/api/dcim/devices/2/", 6 | "name": "test1-core1", 7 | "display_name": "test1-core1" 8 | }, 9 | "name": "et-0/0/0", 10 | "form_factor": { 11 | "value": 1400, 12 | "label": "QSFP+ (40GE)" 13 | }, 14 | "enabled": true, 15 | "lag": { 16 | "id": 223, 17 | "url": "http://localhost:8000/api/dcim/interfaces/223/", 18 | "name": "ae0" 19 | }, 20 | "mtu": null, 21 | "mac_address": null, 22 | "mgmt_only": false, 23 | "description": "", 24 | "is_connected": true, 25 | "connected_endpoints": [ 26 | { 27 | "id": 1, 28 | "url": "http://localhost:8000/api/dcim/devices/1/", 29 | "display": "tst-endpoint", 30 | "name": "tst-endpoint" 31 | } 32 | ], 33 | "connected_endpoints_type": "dcim.device", 34 | "connected_endpoints_reachable": true, 35 | "cable": { 36 | "id": 1, 37 | "url": "http://localhost:8000/api/dcim/cables/1/", 38 | "label": "" 39 | }, 40 | "mode": { 41 | "value": "tagged", 42 | "label": "Tagged", 43 | "id": 200 44 | }, 45 | "untagged_vlan": { 46 | "id": 2, 47 | "url": "http://localhost:8000/api/ipam/vlans/792/", 48 | "vid": 2069, 49 | "name": "v2069", 50 | "display_name": "2069 (v2069)" 51 | }, 52 | "tagged_vlans": [ 53 | { 54 | "id": 3, 55 | "url": "http://localhost:8000/api/ipam/vlans/248/", 56 | "vid": 1210, 57 | "name": "v1210", 58 | "display_name": "1210 (v1210)" 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/interface_connection.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "interface_a": { 4 | "id": 99, 5 | "url": "http://localhost:8000/api/dcim/interfaces/99/", 6 | "device": { 7 | "id": 5, 8 | "url": "http://localhost:8000/api/dcim/devices/5/", 9 | "name": "test1-leaf2", 10 | "display_name": "test1-leaf2" 11 | }, 12 | "name": "et-0/0/48", 13 | "form_factor": 1400, 14 | "mac_address": null, 15 | "mgmt_only": false, 16 | "description": "" 17 | }, 18 | "interface_b": { 19 | "id": 15, 20 | "url": "http://localhost:8000/api/dcim/interfaces/15/", 21 | "device": { 22 | "id": 3, 23 | "url": "http://localhost:8000/api/dcim/devices/3/", 24 | "name": "test1-spine1", 25 | "display_name": "test1-spine1" 26 | }, 27 | "name": "et-0/0/1", 28 | "form_factor": 1400, 29 | "mac_address": null, 30 | "mgmt_only": false, 31 | "description": "" 32 | }, 33 | "connection_status": { 34 | "value": true, 35 | "label": "Connected" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/fixtures/dcim/interface_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device_type": { 4 | "id": 1, 5 | "url": "http://localhost:8000/api/dcim/device-types/1/", 6 | "manufacturer": { 7 | "id": 1, 8 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 9 | "name": "Juniper", 10 | "slug": "juniper" 11 | }, 12 | "model": "MX960", 13 | "slug": "mx960" 14 | }, 15 | "name": "fxp0 (RE0)", 16 | "form_factor": { 17 | "value": 1000, 18 | "label": "1000BASE-T (1GE)" 19 | }, 20 | "mgmt_only": true 21 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/interface_templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 4, 8 | "device_type": { 9 | "id": 2, 10 | "url": "http://localhost:8000/api/dcim/device-types/2/", 11 | "manufacturer": { 12 | "id": 1, 13 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 14 | "name": "Juniper", 15 | "slug": "juniper" 16 | }, 17 | "model": "EX9214", 18 | "slug": "ex9214" 19 | }, 20 | "name": "fxp0 (RE0)", 21 | "form_factor": { 22 | "value": 1000, 23 | "label": "1000BASE-T (1GE)" 24 | }, 25 | "mgmt_only": true 26 | }, 27 | { 28 | "id": 5, 29 | "device_type": { 30 | "id": 2, 31 | "url": "http://localhost:8000/api/dcim/device-types/2/", 32 | "manufacturer": { 33 | "id": 1, 34 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 35 | "name": "Juniper", 36 | "slug": "juniper" 37 | }, 38 | "model": "EX9214", 39 | "slug": "ex9214" 40 | }, 41 | "name": "fxp0 (RE1)", 42 | "form_factor": { 43 | "value": 1000, 44 | "label": "1000BASE-T (1GE)" 45 | }, 46 | "mgmt_only": true 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/interface_trace.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | { 4 | "id": 39126, 5 | "url": "http://localhost:8000/api/dcim/interfaces/39126/", 6 | "device": { 7 | "id": 4747, 8 | "url": "http://localhost:8000/api/dcim/devices/4747/", 9 | "name": "test1-core1", 10 | "display_name": "test1-core1" 11 | }, 12 | "name": "em1", 13 | "cable": 9911, 14 | "connection_status": { 15 | "value": false, 16 | "label": "Not Connected" 17 | } 18 | }, 19 | { 20 | "id": 9911, 21 | "url": "http://localhost:8000/api/dcim/cables/9911/", 22 | "type": "", 23 | "status": "planned", 24 | "label": "", 25 | "color": "", 26 | "length": null, 27 | "length_unit": "" 28 | }, 29 | { 30 | "id": 5583, 31 | "url": "http://localhost:8000/api/dcim/front-ports/5583/", 32 | "device": { 33 | "id": 4430, 34 | "url": "http://localhost:8000/api/dcim/devices/4430/", 35 | "name": "test1-patchpanel1", 36 | "display_name": "test1-patchpanel1" 37 | }, 38 | "name": "pair-11 (ports 21-22)", 39 | "cable": 9911 40 | } 41 | ], 42 | [ 43 | { 44 | "id": 3736, 45 | "url": "http://localhost:8000/api/dcim/rear-ports/3736/", 46 | "device": { 47 | "id": 4430, 48 | "url": "http://localhost:8000/api/dcim/devices/4430/", 49 | "name": "test1-patchpanel1", 50 | "display_name": "test1-patchpanel1" 51 | }, 52 | "name": "port-2", 53 | "cable": 9229 54 | }, 55 | { 56 | "id": 9229, 57 | "url": "http://localhost:8000/api/dcim/cables/9229/", 58 | "type": "mmf-om4", 59 | "status": "planned", 60 | "label": "", 61 | "color": "", 62 | "length": null, 63 | "length_unit": "" 64 | }, 65 | { 66 | "id": 3768, 67 | "url": "http://localhost:8000/api/dcim/rear-ports/3768/", 68 | "device": { 69 | "id": 4436, 70 | "url": "http://localhost:8000/api/dcim/devices/4436/", 71 | "name": "test1-patchpanel2", 72 | "display_name": "test1-patchpanel2" 73 | }, 74 | "name": "port-2", 75 | "cable": 9229 76 | } 77 | ], 78 | [ 79 | { 80 | "id": 5655, 81 | "url": "http://localhost:8000/api/dcim/front-ports/5655/", 82 | "device": { 83 | "id": 4436, 84 | "url": "http://localhost:8000/api/dcim/devices/4436/", 85 | "name": "test1-patchpanel2", 86 | "display_name": "test1-patchpanel2" 87 | }, 88 | "name": "pair-11 (ports 21-22)", 89 | "cable": 9240 90 | }, 91 | { 92 | "id": 9240, 93 | "url": "http://localhost:8000/api/dcim/cables/9240/", 94 | "type": "mmf-om4", 95 | "status": "planned", 96 | "label": "", 97 | "color": "", 98 | "length": null, 99 | "length_unit": "" 100 | }, 101 | { 102 | "id": 35473, 103 | "url": "http://localhost:8000/api/dcim/interfaces/35473/", 104 | "device": { 105 | "id": 3930, 106 | "url": "http://localhost:8000/api/dcim/devices/3930/", 107 | "name": "test1-core2", 108 | "display_name": "test1-core2" 109 | }, 110 | "name": "Ethernet11", 111 | "cable": 9240, 112 | "connection_status": { 113 | "value": false, 114 | "label": "Not Connected" 115 | } 116 | } 117 | ] 118 | ] 119 | -------------------------------------------------------------------------------- /tests/fixtures/dcim/inventory_item.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "Not found." 3 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/inventory_items.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 0, 3 | "next": null, 4 | "previous": null, 5 | "results": [] 6 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/manufacturer.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Juniper", 4 | "slug": "juniper" 5 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/manufacturers.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 3, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "Juniper", 9 | "slug": "juniper" 10 | }, 11 | { 12 | "id": 2, 13 | "name": "Opengear", 14 | "slug": "opengear" 15 | }, 16 | { 17 | "id": 3, 18 | "name": "ServerTech", 19 | "slug": "servertech" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/napalm.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_facts": { 3 | "interface_list": [ 4 | "xe-0/0/0" 5 | ], 6 | "serial_number": "ABC!@#", 7 | "vendor": "PacketPusher", 8 | "os_version": "1.1", 9 | "hostname": "test-1", 10 | "model": "UnobtaniumX", 11 | "fqdn": "None", 12 | "uptime": 2 13 | } 14 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/platform.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Juniper Junos", 4 | "slug": "juniper-junos", 5 | "rpc_client": "juniper-junos" 6 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/platforms.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "Juniper Junos", 9 | "slug": "juniper-junos", 10 | "rpc_client": "juniper-junos" 11 | }, 12 | { 13 | "id": 2, 14 | "name": "Opengear", 15 | "slug": "opengear", 16 | "rpc_client": "opengear" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/power_outlet.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device": { 4 | "id": 11, 5 | "url": "http://localhost:8000/api/dcim/devices/11/", 6 | "name": "test1-pdu1", 7 | "display_name": "test1-pdu1" 8 | }, 9 | "name": "AA1", 10 | "connected_port": 1 11 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/power_outlet_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device_type": { 4 | "id": 6, 5 | "url": "http://localhost:8000/api/dcim/device-types/6/", 6 | "manufacturer": { 7 | "id": 3, 8 | "url": "http://localhost:8000/api/dcim/manufacturers/3/", 9 | "name": "ServerTech", 10 | "slug": "servertech" 11 | }, 12 | "model": "CWG-24VYM415C9", 13 | "slug": "cwg-24vym415c9" 14 | }, 15 | "name": "AA1" 16 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/power_port.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device": { 4 | "id": 1, 5 | "url": "http://localhost:8000/api/dcim/devices/1/", 6 | "name": "test1-edge1", 7 | "display_name": "test1-edge1" 8 | }, 9 | "name": "PEM0", 10 | "power_outlet": { 11 | "id": 25, 12 | "device": { 13 | "id": 11, 14 | "url": "http://localhost:8000/api/dcim/devices/11/", 15 | "name": "test1-pdu1", 16 | "display_name": "test1-pdu1" 17 | }, 18 | "name": "AA1", 19 | "connected_port": 1 20 | }, 21 | "connection_status": true 22 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/power_port_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "device_type": { 4 | "id": 1, 5 | "url": "http://localhost:8000/api/dcim/device-types/1/", 6 | "manufacturer": { 7 | "id": 1, 8 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 9 | "name": "Juniper", 10 | "slug": "juniper" 11 | }, 12 | "model": "MX960", 13 | "slug": "mx960" 14 | }, 15 | "name": "PEM0" 16 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/power_port_templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 13, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 5, 8 | "device_type": { 9 | "id": 2, 10 | "url": "http://localhost:8000/api/dcim/device-types/2/", 11 | "manufacturer": { 12 | "id": 1, 13 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 14 | "name": "Juniper", 15 | "slug": "juniper" 16 | }, 17 | "model": "EX9214", 18 | "slug": "ex9214" 19 | }, 20 | "name": "PEM0" 21 | }, 22 | { 23 | "id": 6, 24 | "device_type": { 25 | "id": 2, 26 | "url": "http://localhost:8000/api/dcim/device-types/2/", 27 | "manufacturer": { 28 | "id": 1, 29 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 30 | "name": "Juniper", 31 | "slug": "juniper" 32 | }, 33 | "model": "EX9214", 34 | "slug": "ex9214" 35 | }, 36 | "name": "PEM1" 37 | }, 38 | { 39 | "id": 7, 40 | "device_type": { 41 | "id": 2, 42 | "url": "http://localhost:8000/api/dcim/device-types/2/", 43 | "manufacturer": { 44 | "id": 1, 45 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 46 | "name": "Juniper", 47 | "slug": "juniper" 48 | }, 49 | "model": "EX9214", 50 | "slug": "ex9214" 51 | }, 52 | "name": "PEM2" 53 | }, 54 | { 55 | "id": 8, 56 | "device_type": { 57 | "id": 2, 58 | "url": "http://localhost:8000/api/dcim/device-types/2/", 59 | "manufacturer": { 60 | "id": 1, 61 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 62 | "name": "Juniper", 63 | "slug": "juniper" 64 | }, 65 | "model": "EX9214", 66 | "slug": "ex9214" 67 | }, 68 | "name": "PEM3" 69 | }, 70 | { 71 | "id": 1, 72 | "device_type": { 73 | "id": 1, 74 | "url": "http://localhost:8000/api/dcim/device-types/1/", 75 | "manufacturer": { 76 | "id": 1, 77 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 78 | "name": "Juniper", 79 | "slug": "juniper" 80 | }, 81 | "model": "MX960", 82 | "slug": "mx960" 83 | }, 84 | "name": "PEM0" 85 | }, 86 | { 87 | "id": 2, 88 | "device_type": { 89 | "id": 1, 90 | "url": "http://localhost:8000/api/dcim/device-types/1/", 91 | "manufacturer": { 92 | "id": 1, 93 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 94 | "name": "Juniper", 95 | "slug": "juniper" 96 | }, 97 | "model": "MX960", 98 | "slug": "mx960" 99 | }, 100 | "name": "PEM1" 101 | }, 102 | { 103 | "id": 3, 104 | "device_type": { 105 | "id": 1, 106 | "url": "http://localhost:8000/api/dcim/device-types/1/", 107 | "manufacturer": { 108 | "id": 1, 109 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 110 | "name": "Juniper", 111 | "slug": "juniper" 112 | }, 113 | "model": "MX960", 114 | "slug": "mx960" 115 | }, 116 | "name": "PEM2" 117 | }, 118 | { 119 | "id": 4, 120 | "device_type": { 121 | "id": 1, 122 | "url": "http://localhost:8000/api/dcim/device-types/1/", 123 | "manufacturer": { 124 | "id": 1, 125 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 126 | "name": "Juniper", 127 | "slug": "juniper" 128 | }, 129 | "model": "MX960", 130 | "slug": "mx960" 131 | }, 132 | "name": "PEM3" 133 | }, 134 | { 135 | "id": 11, 136 | "device_type": { 137 | "id": 3, 138 | "url": "http://localhost:8000/api/dcim/device-types/3/", 139 | "manufacturer": { 140 | "id": 1, 141 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 142 | "name": "Juniper", 143 | "slug": "juniper" 144 | }, 145 | "model": "QFX5100-24Q", 146 | "slug": "qfx5100-24q" 147 | }, 148 | "name": "PSU0" 149 | }, 150 | { 151 | "id": 12, 152 | "device_type": { 153 | "id": 3, 154 | "url": "http://localhost:8000/api/dcim/device-types/3/", 155 | "manufacturer": { 156 | "id": 1, 157 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 158 | "name": "Juniper", 159 | "slug": "juniper" 160 | }, 161 | "model": "QFX5100-24Q", 162 | "slug": "qfx5100-24q" 163 | }, 164 | "name": "PSU1" 165 | }, 166 | { 167 | "id": 9, 168 | "device_type": { 169 | "id": 4, 170 | "url": "http://localhost:8000/api/dcim/device-types/4/", 171 | "manufacturer": { 172 | "id": 1, 173 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 174 | "name": "Juniper", 175 | "slug": "juniper" 176 | }, 177 | "model": "QFX5100-48S", 178 | "slug": "qfx5100-48s" 179 | }, 180 | "name": "PSU0" 181 | }, 182 | { 183 | "id": 13, 184 | "device_type": { 185 | "id": 4, 186 | "url": "http://localhost:8000/api/dcim/device-types/4/", 187 | "manufacturer": { 188 | "id": 1, 189 | "url": "http://localhost:8000/api/dcim/manufacturers/1/", 190 | "name": "Juniper", 191 | "slug": "juniper" 192 | }, 193 | "model": "QFX5100-48S", 194 | "slug": "qfx5100-48s" 195 | }, 196 | "name": "PSU1" 197 | }, 198 | { 199 | "id": 14, 200 | "device_type": { 201 | "id": 5, 202 | "url": "http://localhost:8000/api/dcim/device-types/5/", 203 | "manufacturer": { 204 | "id": 2, 205 | "url": "http://localhost:8000/api/dcim/manufacturers/2/", 206 | "name": "Opengear", 207 | "slug": "opengear" 208 | }, 209 | "model": "CM4148", 210 | "slug": "cm4148" 211 | }, 212 | "name": "PSU" 213 | } 214 | ] 215 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/rack.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "A1R1", 4 | "facility_id": "T23A01", 5 | "display_name": "A1R1 (T23A01)", 6 | "site": { 7 | "id": 1, 8 | "url": "http://localhost:8000/api/dcim/sites/1/", 9 | "name": "TEST1", 10 | "slug": "test1" 11 | }, 12 | "group": null, 13 | "tenant": null, 14 | "role": { 15 | "id": 1, 16 | "url": "http://localhost:8000/api/dcim/rack-roles/1/", 17 | "name": "Compute", 18 | "slug": "compute" 19 | }, 20 | "type": null, 21 | "width": { 22 | "value": 19, 23 | "label": "19 inches" 24 | }, 25 | "u_height": 42, 26 | "desc_units": false, 27 | "comments": "", 28 | "custom_fields": {} 29 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/rack_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "TEST", 4 | "slug": "test", 5 | "site": { 6 | "id": 1, 7 | "url": "http://localhost:8000/api/dcim/sites/1/", 8 | "name": "TEST1", 9 | "slug": "test1" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/rack_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "TEST", 9 | "slug": "test", 10 | "site": { 11 | "id": 1, 12 | "url": "http://localhost:8000/api/dcim/sites/1/", 13 | "name": "TEST1", 14 | "slug": "test1" 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/rack_reservation.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "rack": { 4 | "id": 2, 5 | "url": "http://localhost:8000/api/dcim/racks/2/", 6 | "name": "A1R2", 7 | "display_name": "A1R2 (T24A01)" 8 | }, 9 | "units": [ 10 | 42 11 | ], 12 | "created": "2017-03-22T04:10:47.307156Z", 13 | "user": 1, 14 | "description": "Test Reservation" 15 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/rack_reservations.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "rack": { 9 | "id": 2, 10 | "url": "http://localhost:8000/api/dcim/racks/2/", 11 | "name": "A1R2", 12 | "display_name": "A1R2 (T24A01)" 13 | }, 14 | "units": [ 15 | 42 16 | ], 17 | "created": "2017-03-22T04:10:47.307156Z", 18 | "user": 1, 19 | "description": "Test Reservation" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/rack_role.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Test", 4 | "slug": "test", 5 | "color": "aa1409" 6 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/rack_roles.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "Test", 9 | "slug": "test", 10 | "color": "aa1409" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/rack_u.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 3, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 48, 8 | "name": "U1", 9 | "face": 0, 10 | "device": { 11 | "id": 130, 12 | "url": "http://localhost:8000/api/dcim/devices/1/", 13 | "name": "tst-device1", 14 | "display_name": "tst-device1" 15 | } 16 | }, 17 | { 18 | "id": 47, 19 | "name": "U2", 20 | "face": 0, 21 | "device": null 22 | }, 23 | { 24 | "id": 46, 25 | "name": "U3", 26 | "face": 0, 27 | "device": { 28 | "id": 1859, 29 | "url": "http://localhost:8000/api/dcim/devices/2/", 30 | "name": "tst-device2", 31 | "display_name": "tst-device2" 32 | } 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/racks.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "A1R1", 9 | "facility_id": "T23A01", 10 | "display_name": "A1R1 (T23A01)", 11 | "site": { 12 | "id": 1, 13 | "url": "http://localhost:8000/api/dcim/sites/1/", 14 | "name": "TEST1", 15 | "slug": "test1" 16 | }, 17 | "group": null, 18 | "tenant": null, 19 | "role": null, 20 | "type": null, 21 | "width": { 22 | "value": 19, 23 | "label": "19 inches" 24 | }, 25 | "u_height": 42, 26 | "desc_units": false, 27 | "comments": "", 28 | "custom_fields": {} 29 | }, 30 | { 31 | "id": 2, 32 | "name": "A1R2", 33 | "facility_id": "T24A01", 34 | "display_name": "A1R2 (T24A01)", 35 | "site": { 36 | "id": 1, 37 | "url": "http://localhost:8000/api/dcim/sites/1/", 38 | "name": "TEST1", 39 | "slug": "test1" 40 | }, 41 | "group": null, 42 | "tenant": null, 43 | "role": null, 44 | "type": null, 45 | "width": { 46 | "value": 19, 47 | "label": "19 inches" 48 | }, 49 | "u_height": 42, 50 | "desc_units": false, 51 | "comments": "", 52 | "custom_fields": {} 53 | } 54 | ] 55 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/region.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "TEST", 4 | "slug": "test", 5 | "parent": null 6 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/regions.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "TEST", 9 | "slug": "test", 10 | "parent": null 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/site.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "TEST1", 4 | "slug": "test1", 5 | "region": null, 6 | "tenant": null, 7 | "facility": "Test Facility", 8 | "asn": 65535, 9 | "physical_address": "555 Test Ave.\r\nTest, NY 55555", 10 | "shipping_address": "", 11 | "contact_name": "", 12 | "contact_phone": "", 13 | "contact_email": "", 14 | "comments": "", 15 | "custom_fields": { 16 | "test_custom": "Hello", 17 | "test_selection": { 18 | "value": 2, 19 | "label": "second" 20 | } 21 | }, 22 | "count_prefixes": 2, 23 | "count_vlans": 1, 24 | "count_racks": 2, 25 | "count_devices": 11, 26 | "count_circuits": 0 27 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/sites.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "TEST1", 9 | "slug": "test1", 10 | "region": null, 11 | "tenant": null, 12 | "facility": "Test Facility", 13 | "asn": 65535, 14 | "physical_address": "555 Test Ave.\r\nTest, NY 55555", 15 | "shipping_address": "", 16 | "contact_name": "", 17 | "contact_phone": "", 18 | "contact_email": "", 19 | "comments": "", 20 | "custom_fields": { 21 | "test_custom": "Hello" 22 | }, 23 | "count_prefixes": 2, 24 | "count_vlans": 1, 25 | "count_racks": 2, 26 | "count_devices": 11, 27 | "count_circuits": 0 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /tests/fixtures/dcim/virtual_chassis_device.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "master": { 4 | "id": 5654, 5 | "url": "http://localhost:8000/api/dcim/devices/5654/", 6 | "name": "01-0001-e214-as01 (SW1)", 7 | "display_name": "01-0001-e214-as01 (SW1)" 8 | }, 9 | "domain": "test-domain-1" 10 | } 11 | -------------------------------------------------------------------------------- /tests/fixtures/dcim/virtual_chassis_devices.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "master": { 9 | "id": 5654, 10 | "url": "http://localhost:8000/api/dcim/devices/5654/", 11 | "name": "01-0001-e214-as01 (SW1)", 12 | "display_name": "01-0001-e214-as01 (SW1)" 13 | }, 14 | "domain": "test-domain-1" 15 | }, 16 | { 17 | "id": 2, 18 | "master": { 19 | "id": 5635, 20 | "url": "http://localhost:8000/netbox/api/dcim/devices/5635/", 21 | "name": "hercules.router.com (VSS-SW1)", 22 | "display_name": "hercules.router.com (VSS-SW1)" 23 | }, 24 | "domain": "test-domain-2" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tests/fixtures/ipam/aggregate.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "family": 4, 4 | "prefix": "10.0.0.0/8", 5 | "rir": { 6 | "id": 1, 7 | "url": "http://localhost:8000/api/ipam/rirs/1/", 8 | "name": "RFC1918", 9 | "slug": "rfc1918" 10 | }, 11 | "date_added": null, 12 | "description": "", 13 | "custom_fields": {} 14 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/aggregates.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "family": 4, 9 | "prefix": "10.0.0.0/8", 10 | "rir": { 11 | "id": 1, 12 | "url": "http://localhost:8000/api/ipam/rirs/1/", 13 | "name": "RFC1918", 14 | "slug": "rfc1918" 15 | }, 16 | "date_added": null, 17 | "description": "", 18 | "custom_fields": {} 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/available-ips-post.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "address": "10.1.1.1/32", 4 | "vrf": null, 5 | "tenant": null, 6 | "status": 1, 7 | "role": null, 8 | "interface": null, 9 | "description": "", 10 | "nat_inside": null 11 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/available-ips.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "family": 4, 4 | "address": "10.1.1.2/27", 5 | "vrf": null 6 | }, 7 | { 8 | "family": 4, 9 | "address": "10.1.1.3/27", 10 | "vrf": null 11 | }, 12 | { 13 | "family": 4, 14 | "address": "10.1.1.7/27", 15 | "vrf": null 16 | } 17 | ] -------------------------------------------------------------------------------- /tests/fixtures/ipam/available-prefixes-post.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 4, 4 | "prefix": "10.1.1.0/30", 5 | "site": null, 6 | "vrf": null, 7 | "tenant": null, 8 | "vlan": null, 9 | "status": 1, 10 | "role": null, 11 | "is_pool": false, 12 | "description": "" 13 | } 14 | ] -------------------------------------------------------------------------------- /tests/fixtures/ipam/available-prefixes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "family": 4, 4 | "prefix": "10.1.1.0/24", 5 | "vrf": null 6 | } 7 | ] -------------------------------------------------------------------------------- /tests/fixtures/ipam/ip_address.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "family": 4, 4 | "address": "10.0.255.1/32", 5 | "vrf": null, 6 | "tenant": null, 7 | "status": { 8 | "value": 1, 9 | "label": "Active" 10 | }, 11 | "interface": { 12 | "id": 3, 13 | "device": { 14 | "id": 1, 15 | "url": "http://localhost:8000/api/dcim/devices/1/", 16 | "name": "test1-edge1", 17 | "display_name": "test1-edge1" 18 | }, 19 | "name": "lo0", 20 | "form_factor": { 21 | "value": 0, 22 | "label": "Virtual" 23 | }, 24 | "lag": null, 25 | "mac_address": null, 26 | "mgmt_only": false, 27 | "description": "", 28 | "connection": null, 29 | "connected_interface": null 30 | }, 31 | "description": "", 32 | "nat_inside": null, 33 | "nat_outside": null, 34 | "custom_fields": {} 35 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/prefix.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "family": 4, 4 | "prefix": "10.1.1.0/24", 5 | "site": { 6 | "id": 1, 7 | "url": "http://localhost:8000/api/dcim/sites/1/", 8 | "name": "TEST1", 9 | "slug": "test1" 10 | }, 11 | "vrf": null, 12 | "tenant": null, 13 | "vlan": null, 14 | "status": { 15 | "value": 1, 16 | "label": "Active" 17 | }, 18 | "role": { 19 | "id": 1, 20 | "url": "http://localhost:8000/api/ipam/roles/1/", 21 | "name": "Lab Network", 22 | "slug": "lab-network" 23 | }, 24 | "is_pool": false, 25 | "description": "", 26 | "custom_fields": {} 27 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/prefixes.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 2, 8 | "family": 4, 9 | "prefix": "10.0.255.0/24", 10 | "site": { 11 | "id": 1, 12 | "url": "http://localhost:8000/api/dcim/sites/1/", 13 | "name": "TEST1", 14 | "slug": "test1" 15 | }, 16 | "vrf": null, 17 | "tenant": null, 18 | "vlan": null, 19 | "status": { 20 | "value": 1, 21 | "label": "Active" 22 | }, 23 | "role": { 24 | "id": 1, 25 | "url": "http://localhost:8000/api/ipam/roles/1/", 26 | "name": "Lab Network", 27 | "slug": "lab-network" 28 | }, 29 | "is_pool": false, 30 | "description": "", 31 | "custom_fields": {} 32 | }, 33 | { 34 | "id": 1, 35 | "family": 4, 36 | "prefix": "10.1.1.0/24", 37 | "site": { 38 | "id": 1, 39 | "url": "http://localhost:8000/api/dcim/sites/1/", 40 | "name": "TEST1", 41 | "slug": "test1" 42 | }, 43 | "vrf": null, 44 | "tenant": null, 45 | "vlan": null, 46 | "status": { 47 | "value": 1, 48 | "label": "Active" 49 | }, 50 | "role": { 51 | "id": 1, 52 | "url": "http://localhost:8000/api/ipam/roles/1/", 53 | "name": "Lab Network", 54 | "slug": "lab-network" 55 | }, 56 | "is_pool": false, 57 | "description": "", 58 | "custom_fields": {} 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/rir.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "RFC1918", 4 | "slug": "rfc1918", 5 | "is_private": false 6 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/rirs.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "RFC1918", 9 | "slug": "rfc1918", 10 | "is_private": false 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/role.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Lab Network", 4 | "slug": "lab-network", 5 | "weight": 1000 6 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/roles.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "Lab Network", 9 | "slug": "lab-network", 10 | "weight": 1000 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/vlan.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 3, 3 | "site": { 4 | "id": 1, 5 | "url": "http://localhost:8000/api/dcim/sites/1/", 6 | "name": "TEST1", 7 | "slug": "test1" 8 | }, 9 | "group": null, 10 | "vid": 1210, 11 | "name": "v1210", 12 | "tenant": null, 13 | "status": { 14 | "value": 1, 15 | "label": "Active" 16 | }, 17 | "role": { 18 | "id": 1, 19 | "url": "http://localhost:8000/api/ipam/roles/1/", 20 | "name": "Lab Network", 21 | "slug": "lab-network" 22 | }, 23 | "description": "", 24 | "display_name": "1210 (v1210)", 25 | "custom_fields": {} 26 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/vlan_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "TEST", 4 | "slug": "test", 5 | "site": { 6 | "id": 1, 7 | "url": "http://localhost:8000/api/dcim/sites/1/", 8 | "name": "TEST1", 9 | "slug": "test1" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/vlan_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "TEST", 9 | "slug": "test", 10 | "site": { 11 | "id": 1, 12 | "url": "http://localhost:8000/api/dcim/sites/1/", 13 | "name": "TEST1", 14 | "slug": "test1" 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/vlans.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 3, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "site": { 9 | "id": 1, 10 | "url": "http://localhost:8000/api/dcim/sites/1/", 11 | "name": "TEST1", 12 | "slug": "test1" 13 | }, 14 | "group": null, 15 | "vid": 999, 16 | "name": "TEST", 17 | "tenant": null, 18 | "status": { 19 | "value": 1, 20 | "label": "Active" 21 | }, 22 | "role": { 23 | "id": 1, 24 | "url": "http://localhost:8000/api/ipam/roles/1/", 25 | "name": "Lab Network", 26 | "slug": "lab-network" 27 | }, 28 | "description": "", 29 | "display_name": "999 (TEST)", 30 | "custom_fields": {} 31 | }, 32 | { 33 | "id": 2, 34 | "site": { 35 | "id": 1, 36 | "url": "http://localhost:8000/api/dcim/sites/1/", 37 | "name": "TEST1", 38 | "slug": "test1" 39 | }, 40 | "group": null, 41 | "vid": 2069, 42 | "name": "v2069", 43 | "tenant": null, 44 | "status": { 45 | "value": 1, 46 | "label": "Active" 47 | }, 48 | "role": { 49 | "id": 1, 50 | "url": "http://localhost:8000/api/ipam/roles/1/", 51 | "name": "Lab Network", 52 | "slug": "lab-network" 53 | }, 54 | "description": "", 55 | "display_name": "2069 (v2069)", 56 | "custom_fields": {} 57 | }, 58 | { 59 | "id": 3, 60 | "site": { 61 | "id": 1, 62 | "url": "http://localhost:8000/api/dcim/sites/1/", 63 | "name": "TEST1", 64 | "slug": "test1" 65 | }, 66 | "group": null, 67 | "vid": 1210, 68 | "name": "v1210", 69 | "tenant": null, 70 | "status": { 71 | "value": 1, 72 | "label": "Active" 73 | }, 74 | "role": { 75 | "id": 1, 76 | "url": "http://localhost:8000/api/ipam/roles/1/", 77 | "name": "Lab Network", 78 | "slug": "lab-network" 79 | }, 80 | "description": "", 81 | "display_name": "1210 (v1210)", 82 | "custom_fields": {} 83 | } 84 | ] 85 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/vrf.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "TEST", 4 | "rd": "65535:1", 5 | "tenant": null, 6 | "enforce_unique": true, 7 | "description": "", 8 | "custom_fields": {} 9 | } -------------------------------------------------------------------------------- /tests/fixtures/ipam/vrfs.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "TEST", 9 | "rd": "65535:1", 10 | "tenant": null, 11 | "enforce_unique": true, 12 | "description": "", 13 | "custom_fields": {} 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /tests/fixtures/tenancy/tenant.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "TEST Tenant 1", 4 | "slug": "test-tenant-1", 5 | "group": { 6 | "id": 1, 7 | "url": "http://localhost:8000/api/tenancy/tenant-groups/1/", 8 | "name": "TEST Group", 9 | "slug": "test-group" 10 | }, 11 | "description": "", 12 | "comments": "", 13 | "custom_fields": {} 14 | } -------------------------------------------------------------------------------- /tests/fixtures/tenancy/tenant_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "TEST Group", 4 | "slug": "test-group" 5 | } -------------------------------------------------------------------------------- /tests/fixtures/tenancy/tenant_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "TEST Group", 9 | "slug": "test-group" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/fixtures/tenancy/tenants.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "TEST Tenant 1", 9 | "slug": "test-tenant-1", 10 | "group": { 11 | "id": 1, 12 | "url": "http://localhost:8000/api/tenancy/tenant-groups/1/", 13 | "name": "TEST Group", 14 | "slug": "test-group" 15 | }, 16 | "description": "", 17 | "comments": "", 18 | "custom_fields": {} 19 | }, 20 | { 21 | "id": 2, 22 | "name": "TEST Tenant 2", 23 | "slug": "test-tenant-2", 24 | "group": { 25 | "id": 1, 26 | "url": "http://localhost:8000/api/tenancy/tenant-groups/1/", 27 | "name": "TEST Group", 28 | "slug": "test-group" 29 | }, 30 | "description": "", 31 | "comments": "", 32 | "custom_fields": {} 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /tests/fixtures/users/group.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "usergroup1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/users/groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "usergroup1" 9 | }, 10 | { 11 | "id": 2, 12 | "name": "usergroup2" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/users/permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "permission1", 4 | "users": [ 5 | { 6 | "username": "user1" 7 | } 8 | ], 9 | "constraints": [ 10 | { 11 | "status": "active" 12 | }, 13 | { 14 | "region__name": "Europe" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/users/permissions.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "permission1" 9 | }, 10 | { 11 | "id": 2, 12 | "name": "permission2" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/users/unknown_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "display": "Unknown object" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/users/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "username": "user1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/users/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "username": "user1" 9 | }, 10 | { 11 | "id": 2, 12 | "username": "user2" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/virtualization/cluster.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "vm-test-cluster", 4 | "type": { 5 | "id": 1, 6 | "url": "http://localhost:8000/api/virtualization/cluster-types/1/", 7 | "name": "vm-test-type", 8 | "slug": "vm-test-type" 9 | }, 10 | "group": { 11 | "id": 1, 12 | "url": "http://localhost:8000/api/virtualization/cluster-groups/1/", 13 | "name": "vm-test-group", 14 | "slug": "vm-test-group" 15 | }, 16 | "site": { 17 | "id": 1, 18 | "url": "http://localhost:8000/api/dcim/sites/1/", 19 | "name": "TEST1", 20 | "slug": "test1" 21 | }, 22 | "comments": "", 23 | "custom_fields": {} 24 | } -------------------------------------------------------------------------------- /tests/fixtures/virtualization/cluster_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "vm-test-group", 4 | "slug": "vm-test-group" 5 | } -------------------------------------------------------------------------------- /tests/fixtures/virtualization/cluster_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "vm-test-group", 9 | "slug": "vm-test-group" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/fixtures/virtualization/cluster_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "vm-test-type", 4 | "slug": "vm-test-type" 5 | } -------------------------------------------------------------------------------- /tests/fixtures/virtualization/cluster_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "vm-test-type", 9 | "slug": "vm-test-type" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/fixtures/virtualization/clusters.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "vm-test-cluster", 9 | "type": { 10 | "id": 1, 11 | "url": "http://localhost:8000/api/virtualization/cluster-types/1/", 12 | "name": "vm-test-type", 13 | "slug": "vm-test-type" 14 | }, 15 | "group": { 16 | "id": 1, 17 | "url": "http://localhost:8000/api/virtualization/cluster-groups/1/", 18 | "name": "vm-test-group", 19 | "slug": "vm-test-group" 20 | }, 21 | "site": { 22 | "id": 1, 23 | "url": "http://localhost:8000/api/dcim/sites/1/", 24 | "name": "TEST1", 25 | "slug": "test1" 26 | }, 27 | "comments": "", 28 | "custom_fields": {} 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /tests/fixtures/virtualization/interface.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 223, 3 | "name": "eth0", 4 | "virtual_machine": { 5 | "id": 1, 6 | "url": "http://localhost:8000/api/virtualization/virtual-machines/1/", 7 | "name": "vm-test01" 8 | }, 9 | "enabled": true, 10 | "mac_address": null, 11 | "mtu": null, 12 | "description": "" 13 | } -------------------------------------------------------------------------------- /tests/fixtures/virtualization/interfaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 223, 8 | "name": "eth0", 9 | "virtual_machine": { 10 | "id": 1, 11 | "url": "http://localhost:8000/api/virtualization/virtual-machines/1/", 12 | "name": "vm-test01" 13 | }, 14 | "enabled": true, 15 | "mac_address": null, 16 | "mtu": null, 17 | "description": "" 18 | }, 19 | { 20 | "id": 224, 21 | "name": "eth1", 22 | "virtual_machine": { 23 | "id": 1, 24 | "url": "http://localhost:8000/api/virtualization/virtual-machines/1/", 25 | "name": "vm-test01" 26 | }, 27 | "enabled": true, 28 | "mac_address": null, 29 | "mtu": null, 30 | "description": "" 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /tests/fixtures/virtualization/virtual_machine.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "vm-test01", 4 | "status": { 5 | "value": 1, 6 | "label": "Active" 7 | }, 8 | "cluster": { 9 | "id": 1, 10 | "url": "http://localhost:8000/api/virtualization/clusters/1/", 11 | "name": "vm-test-cluster" 12 | }, 13 | "role": null, 14 | "tenant": null, 15 | "platform": null, 16 | "primary_ip4": null, 17 | "primary_ip6": null, 18 | "vcpus": 2, 19 | "memory": 1024, 20 | "disk": 500, 21 | "comments": "", 22 | "custom_fields": {} 23 | } 24 | -------------------------------------------------------------------------------- /tests/fixtures/virtualization/virtual_machines.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "name": "vm-test01", 9 | "status": { 10 | "value": 1, 11 | "label": "Active" 12 | }, 13 | "cluster": { 14 | "id": 1, 15 | "url": "http://localhost:8000/api/virtualization/clusters/1/", 16 | "name": "vm-test-cluster" 17 | }, 18 | "role": null, 19 | "tenant": null, 20 | "platform": null, 21 | "primary_ip4": null, 22 | "primary_ip6": null, 23 | "vcpus": 2, 24 | "memory": 1024, 25 | "disk": 500, 26 | "comments": "", 27 | "custom_fields": {} 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /tests/fixtures/wireless/wireless_lan.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "ssid": "SSID 1" 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/wireless/wireless_lans.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "ssid": "SSID 1" 9 | }, 10 | { 11 | "id": 2, 12 | "ssid": "SSID 2" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tests/integration/test_ipam.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from packaging import version 3 | 4 | 5 | @pytest.mark.usefixtures("init") 6 | class BaseTest: 7 | app = "ipam" 8 | 9 | def _init_helper( 10 | self, 11 | request, 12 | fixture, 13 | update_field=None, 14 | filter_kwargs=None, 15 | endpoint=None, 16 | str_repr=None, 17 | ): 18 | request.cls.endpoint = endpoint 19 | request.cls.fixture = fixture 20 | request.cls.update_field = update_field 21 | request.cls.filter_kwargs = filter_kwargs 22 | request.cls.str_repr = str_repr 23 | 24 | def test_create(self): 25 | assert self.fixture 26 | 27 | def test_str(self): 28 | if self.str_repr: 29 | test = str(self.fixture) 30 | assert test == self.str_repr 31 | 32 | def test_update_fixture(self): 33 | if self.update_field: 34 | setattr(self.fixture, self.update_field, "Test Value") 35 | assert self.fixture.save() 36 | 37 | def test_get_fixture_by_id(self, api): 38 | test = getattr(getattr(api, self.app), self.endpoint).get(self.fixture.id) 39 | assert test 40 | if self.update_field: 41 | assert getattr(test, self.update_field) == "Test Value" 42 | 43 | def test_get_fixture_by_kwarg(self, api): 44 | test = getattr(getattr(api, self.app), self.endpoint).get(**self.filter_kwargs) 45 | assert test 46 | if self.update_field: 47 | assert getattr(test, self.update_field) == "Test Value" 48 | 49 | def test_filter_fixture(self, api): 50 | test = list( 51 | getattr(getattr(api, self.app), self.endpoint).filter(**self.filter_kwargs) 52 | )[0] 53 | assert test 54 | if self.update_field: 55 | assert getattr(test, self.update_field) == "Test Value" 56 | 57 | 58 | @pytest.fixture(scope="module") 59 | def rir(api, site): 60 | ret = api.ipam.rirs.create( 61 | name="ministry-of-silly-walks", slug="ministry-of-silly-walks" 62 | ) 63 | yield ret 64 | ret.delete() 65 | 66 | 67 | class TestRIR(BaseTest): 68 | @pytest.fixture(scope="class") 69 | def init(self, request, rir, nb_version): 70 | self._init_helper( 71 | request, 72 | rir, 73 | filter_kwargs={"name": "ministry-of-silly-walks"}, 74 | update_field="description" if nb_version >= version.parse("2.8") else None, 75 | endpoint="rirs", 76 | ) 77 | 78 | 79 | class TestAggregate(BaseTest): 80 | @pytest.fixture(scope="class") 81 | def aggregate(self, api, rir): 82 | ret = api.ipam.aggregates.create(prefix="192.0.2.0/24", rir=rir.id) 83 | yield ret 84 | ret.delete() 85 | 86 | @pytest.fixture(scope="class") 87 | def init(self, request, aggregate): 88 | self._init_helper( 89 | request, 90 | aggregate, 91 | filter_kwargs={"prefix": "192.0.2.0/24"}, 92 | update_field="description", 93 | endpoint="aggregates", 94 | ) 95 | 96 | 97 | class TestPrefix(BaseTest): 98 | @pytest.fixture(scope="class") 99 | def prefix(self, api): 100 | ret = api.ipam.prefixes.create(prefix="192.0.2.0/24") 101 | yield ret 102 | ret.delete() 103 | 104 | @pytest.fixture(scope="class") 105 | def init(self, request, prefix): 106 | self._init_helper( 107 | request, 108 | prefix, 109 | filter_kwargs={"prefix": "192.0.2.0/24"}, 110 | update_field="description", 111 | endpoint="prefixes", 112 | ) 113 | 114 | 115 | class TestIpAddress(BaseTest): 116 | @pytest.fixture(scope="class") 117 | def ip(self, api): 118 | ret = api.ipam.ip_addresses.create(address="192.0.2.1/24") 119 | yield ret 120 | ret.delete() 121 | 122 | @pytest.fixture(scope="class") 123 | def init(self, request, ip): 124 | self._init_helper( 125 | request, 126 | ip, 127 | filter_kwargs={"q": "192.0.2.1/24"}, 128 | update_field="description", 129 | endpoint="ip_addresses", 130 | ) 131 | 132 | 133 | class TestRole(BaseTest): 134 | @pytest.fixture(scope="class") 135 | def role(self, api): 136 | ret = api.ipam.roles.create(name="test-role", slug="test-role") 137 | yield ret 138 | ret.delete() 139 | 140 | @pytest.fixture(scope="class") 141 | def init(self, request, role): 142 | self._init_helper( 143 | request, 144 | role, 145 | filter_kwargs={"name": "test-role"}, 146 | update_field="description", 147 | endpoint="roles", 148 | ) 149 | 150 | 151 | class TestVlan(BaseTest): 152 | @pytest.fixture(scope="class") 153 | def vlan(self, api): 154 | ret = api.ipam.vlans.create(vid=123, name="test-vlan") 155 | yield ret 156 | ret.delete() 157 | 158 | @pytest.fixture(scope="class") 159 | def init(self, request, vlan): 160 | self._init_helper( 161 | request, 162 | vlan, 163 | filter_kwargs={"name": "test-vlan"}, 164 | update_field="description", 165 | endpoint="vlans", 166 | ) 167 | 168 | 169 | class TestVRF(BaseTest): 170 | @pytest.fixture(scope="class") 171 | def vrf(self, api): 172 | ret = api.ipam.vrfs.create(name="test-vrf", rd="192.0.2.1:1234") 173 | yield ret 174 | ret.delete() 175 | 176 | @pytest.fixture(scope="class") 177 | def init(self, request, vrf): 178 | self._init_helper( 179 | request, 180 | vrf, 181 | filter_kwargs={"name": "test-vrf"}, 182 | update_field="description", 183 | endpoint="vrfs", 184 | ) 185 | -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | import pynetbox 5 | 6 | from .util import Response 7 | 8 | host = "http://localhost:8000" 9 | 10 | def_kwargs = { 11 | "token": "abc123", 12 | } 13 | 14 | # Keys are app names, values are arbitrarily selected endpoints 15 | # We use dcim and ipam since they have unique app classes 16 | # and circuits because it does not. We don't add other apps/endpoints 17 | # beyond 'circuits' as they all use the same code as each other 18 | endpoints = { 19 | "dcim": "devices", 20 | "ipam": "prefixes", 21 | "circuits": "circuits", 22 | } 23 | 24 | 25 | class ApiTestCase(unittest.TestCase): 26 | @patch( 27 | "requests.sessions.Session.post", 28 | return_value=Response(), 29 | ) 30 | def test_get(self, *_): 31 | api = pynetbox.api(host, **def_kwargs) 32 | self.assertTrue(api) 33 | 34 | @patch( 35 | "requests.sessions.Session.post", 36 | return_value=Response(), 37 | ) 38 | def test_sanitize_url(self, *_): 39 | api = pynetbox.api("http://localhost:8000/", **def_kwargs) 40 | self.assertTrue(api) 41 | self.assertEqual(api.base_url, "http://localhost:8000/api") 42 | 43 | 44 | class ApiVersionTestCase(unittest.TestCase): 45 | class ResponseHeadersWithVersion: 46 | headers = {"API-Version": "1.999"} 47 | ok = True 48 | 49 | @patch( 50 | "requests.sessions.Session.get", 51 | return_value=ResponseHeadersWithVersion(), 52 | ) 53 | def test_api_version(self, *_): 54 | api = pynetbox.api( 55 | host, 56 | ) 57 | self.assertEqual(api.version, "1.999") 58 | 59 | class ResponseHeadersWithoutVersion: 60 | headers = {} 61 | ok = True 62 | 63 | @patch( 64 | "requests.sessions.Session.get", 65 | return_value=ResponseHeadersWithoutVersion(), 66 | ) 67 | def test_api_version_not_found(self, *_): 68 | api = pynetbox.api( 69 | host, 70 | ) 71 | self.assertEqual(api.version, "") 72 | 73 | 74 | class ApiStatusTestCase(unittest.TestCase): 75 | class ResponseWithStatus: 76 | ok = True 77 | 78 | def json(self): 79 | return { 80 | "netbox-version": "0.9.9", 81 | } 82 | 83 | @patch( 84 | "requests.sessions.Session.get", 85 | return_value=ResponseWithStatus(), 86 | ) 87 | def test_api_status(self, *_): 88 | api = pynetbox.api( 89 | host, 90 | ) 91 | self.assertEqual(api.status()["netbox-version"], "0.9.9") 92 | 93 | 94 | class ApiCreateTokenTestCase(unittest.TestCase): 95 | @patch( 96 | "requests.sessions.Session.post", 97 | return_value=Response(fixture="api/token_provision.json"), 98 | ) 99 | def test_create_token(self, *_): 100 | api = pynetbox.api(host) 101 | token = api.create_token("user", "pass") 102 | self.assertTrue(isinstance(token, pynetbox.core.response.Record)) 103 | self.assertEqual(token.key, "1234567890123456789012345678901234567890") 104 | self.assertEqual(api.token, "1234567890123456789012345678901234567890") 105 | -------------------------------------------------------------------------------- /tests/test_app.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | import pynetbox 5 | 6 | host = "http://localhost:8000" 7 | 8 | def_kwargs = { 9 | "token": "abc123", 10 | } 11 | 12 | 13 | class AppConfigTestCase(unittest.TestCase): 14 | @patch( 15 | "pynetbox.core.query.Request.get", 16 | return_value={ 17 | "tables": { 18 | "DeviceTable": { 19 | "columns": [ 20 | "name", 21 | "status", 22 | "tenant", 23 | "tags", 24 | ], 25 | }, 26 | }, 27 | }, 28 | ) 29 | def test_config(self, *_): 30 | api = pynetbox.api(host, **def_kwargs) 31 | config = api.users.config() 32 | self.assertEqual(sorted(config.keys()), ["tables"]) 33 | self.assertEqual( 34 | sorted(config["tables"]["DeviceTable"]["columns"]), 35 | ["name", "status", "tags", "tenant"], 36 | ) 37 | 38 | 39 | class PluginAppTestCase(unittest.TestCase): 40 | @patch( 41 | "pynetbox.core.query.Request.get", 42 | return_value=[ 43 | { 44 | "name": "test_plugin", 45 | "package": "netbox_test_plugin", 46 | } 47 | ], 48 | ) 49 | def test_installed_plugins(self, *_): 50 | api = pynetbox.api(host, **def_kwargs) 51 | plugins = api.plugins.installed_plugins() 52 | self.assertEqual(len(plugins), 1) 53 | self.assertEqual(plugins[0]["name"], "test_plugin") 54 | 55 | def test_plugin_app_name(self, *_): 56 | api = pynetbox.api(host, **def_kwargs) 57 | test_plugin = api.plugins.test_plugin 58 | self.assertEqual(test_plugin.name, "plugins/test-plugin") 59 | -------------------------------------------------------------------------------- /tests/test_circuits.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | import pynetbox 5 | 6 | from .util import Response 7 | 8 | api = pynetbox.api( 9 | "http://localhost:8000", 10 | ) 11 | 12 | nb = api.circuits 13 | 14 | HEADERS = {"accept": "application/json"} 15 | 16 | 17 | class Generic: 18 | class Tests(unittest.TestCase): 19 | name = "" 20 | ret = pynetbox.core.response.Record 21 | app = "circuits" 22 | 23 | def test_get_all(self): 24 | with patch( 25 | "requests.sessions.Session.get", 26 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 27 | ) as mock: 28 | ret = list(getattr(nb, self.name).all()) 29 | self.assertTrue(ret) 30 | self.assertTrue(isinstance(ret[0], self.ret)) 31 | mock.assert_called_with( 32 | "http://localhost:8000/api/{}/{}/".format( 33 | self.app, self.name.replace("_", "-") 34 | ), 35 | params={"limit": 0}, 36 | json=None, 37 | headers=HEADERS, 38 | ) 39 | 40 | def test_filter(self): 41 | with patch( 42 | "requests.sessions.Session.get", 43 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 44 | ) as mock: 45 | ret = list(getattr(nb, self.name).filter(name="test")) 46 | self.assertTrue(ret) 47 | self.assertTrue(isinstance(ret[0], self.ret)) 48 | mock.assert_called_with( 49 | "http://localhost:8000/api/{}/{}/".format( 50 | self.app, self.name.replace("_", "-") 51 | ), 52 | params={"name": "test", "limit": 0}, 53 | json=None, 54 | headers=HEADERS, 55 | ) 56 | 57 | def test_get(self): 58 | with patch( 59 | "requests.sessions.Session.get", 60 | return_value=Response( 61 | fixture="{}/{}.json".format(self.app, self.name[:-1]) 62 | ), 63 | ) as mock: 64 | ret = getattr(nb, self.name).get(1) 65 | self.assertTrue(ret) 66 | self.assertTrue(isinstance(ret, self.ret)) 67 | mock.assert_called_with( 68 | "http://localhost:8000/api/{}/{}/1/".format( 69 | self.app, self.name.replace("_", "-") 70 | ), 71 | params={}, 72 | json=None, 73 | headers=HEADERS, 74 | ) 75 | 76 | 77 | class CircuitsTestCase(Generic.Tests): 78 | name = "circuits" 79 | 80 | @patch( 81 | "requests.sessions.Session.get", 82 | return_value=Response(fixture="circuits/circuit.json"), 83 | ) 84 | def test_repr(self, _): 85 | test = nb.circuits.get(1) 86 | self.assertEqual(str(test), "123456") 87 | 88 | 89 | class ProviderTestCase(Generic.Tests): 90 | name = "providers" 91 | 92 | 93 | class CircuitTypeTestCase(Generic.Tests): 94 | name = "circuit_types" 95 | 96 | 97 | class CircuitTerminationsTestCase(Generic.Tests): 98 | name = "circuit_terminations" 99 | 100 | @patch( 101 | "requests.sessions.Session.get", 102 | return_value=Response(fixture="circuits/circuit_termination.json"), 103 | ) 104 | def test_repr(self, _): 105 | test = nb.circuit_terminations.get(1) 106 | self.assertEqual(str(test), "123456") 107 | -------------------------------------------------------------------------------- /tests/test_tenancy.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | import pynetbox 5 | 6 | from .util import Response 7 | 8 | api = pynetbox.api( 9 | "http://localhost:8000", 10 | ) 11 | 12 | nb = api.tenancy 13 | 14 | HEADERS = {"accept": "application/json"} 15 | 16 | 17 | class Generic: 18 | class Tests(unittest.TestCase): 19 | name = "" 20 | ret = pynetbox.core.response.Record 21 | app = "tenancy" 22 | 23 | def test_get_all(self): 24 | with patch( 25 | "requests.sessions.Session.get", 26 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 27 | ) as mock: 28 | ret = list(getattr(nb, self.name).all()) 29 | self.assertTrue(ret) 30 | self.assertTrue(isinstance(ret[0], self.ret)) 31 | mock.assert_called_with( 32 | "http://localhost:8000/api/{}/{}/".format( 33 | self.app, self.name.replace("_", "-") 34 | ), 35 | params={"limit": 0}, 36 | json=None, 37 | headers=HEADERS, 38 | ) 39 | 40 | def test_filter(self): 41 | with patch( 42 | "requests.sessions.Session.get", 43 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 44 | ) as mock: 45 | ret = list(getattr(nb, self.name).filter(name="test")) 46 | self.assertTrue(ret) 47 | self.assertTrue(isinstance(ret[0], self.ret)) 48 | mock.assert_called_with( 49 | "http://localhost:8000/api/{}/{}/".format( 50 | self.app, self.name.replace("_", "-") 51 | ), 52 | params={"name": "test", "limit": 0}, 53 | json=None, 54 | headers=HEADERS, 55 | ) 56 | 57 | def test_get(self): 58 | with patch( 59 | "requests.sessions.Session.get", 60 | return_value=Response( 61 | fixture="{}/{}.json".format(self.app, self.name[:-1]) 62 | ), 63 | ) as mock: 64 | ret = getattr(nb, self.name).get(1) 65 | self.assertTrue(ret) 66 | self.assertTrue(isinstance(ret, self.ret)) 67 | mock.assert_called_with( 68 | "http://localhost:8000/api/{}/{}/1/".format( 69 | self.app, self.name.replace("_", "-") 70 | ), 71 | params={}, 72 | json=None, 73 | headers=HEADERS, 74 | ) 75 | 76 | 77 | class TenantsTestCase(Generic.Tests): 78 | name = "tenants" 79 | 80 | 81 | class TenantGroupsTestCase(Generic.Tests): 82 | name = "tenant_groups" 83 | -------------------------------------------------------------------------------- /tests/test_users.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | import pynetbox 5 | 6 | from .util import Response 7 | 8 | api = pynetbox.api( 9 | "http://localhost:8000", 10 | ) 11 | 12 | nb = api.users 13 | 14 | HEADERS = {"accept": "application/json"} 15 | 16 | 17 | class Generic: 18 | class Tests(unittest.TestCase): 19 | name = "" 20 | ret = pynetbox.core.response.Record 21 | app = "users" 22 | 23 | def test_get_all(self): 24 | with patch( 25 | "requests.sessions.Session.get", 26 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 27 | ) as mock: 28 | ret = list(getattr(nb, self.name).all()) 29 | self.assertTrue(ret) 30 | self.assertTrue(isinstance(ret[0], self.ret)) 31 | mock.assert_called_with( 32 | "http://localhost:8000/api/{}/{}/".format( 33 | self.app, self.name.replace("_", "-") 34 | ), 35 | params={"limit": 0}, 36 | json=None, 37 | headers=HEADERS, 38 | ) 39 | 40 | def test_filter(self): 41 | with patch( 42 | "requests.sessions.Session.get", 43 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 44 | ) as mock: 45 | ret = list(getattr(nb, self.name).filter(name="test")) 46 | self.assertTrue(ret) 47 | self.assertTrue(isinstance(ret[0], self.ret)) 48 | mock.assert_called_with( 49 | "http://localhost:8000/api/{}/{}/".format( 50 | self.app, self.name.replace("_", "-") 51 | ), 52 | params={"name": "test", "limit": 0}, 53 | json=None, 54 | headers=HEADERS, 55 | ) 56 | 57 | def test_get(self): 58 | with patch( 59 | "requests.sessions.Session.get", 60 | return_value=Response( 61 | fixture="{}/{}.json".format(self.app, self.name[:-1]) 62 | ), 63 | ) as mock: 64 | ret = getattr(nb, self.name).get(1) 65 | self.assertTrue(ret) 66 | self.assertTrue(isinstance(ret, self.ret)) 67 | mock.assert_called_with( 68 | "http://localhost:8000/api/{}/{}/1/".format( 69 | self.app, self.name.replace("_", "-") 70 | ), 71 | params={}, 72 | json=None, 73 | headers=HEADERS, 74 | ) 75 | 76 | 77 | class UsersTestCase(Generic.Tests): 78 | name = "users" 79 | 80 | @patch( 81 | "requests.sessions.Session.get", 82 | return_value=Response(fixture="users/user.json"), 83 | ) 84 | def test_repr(self, _): 85 | test = nb.users.get(1) 86 | self.assertEqual(type(test), pynetbox.models.users.Users) 87 | self.assertEqual(str(test), "user1") 88 | 89 | 90 | class GroupsTestCase(Generic.Tests): 91 | name = "groups" 92 | 93 | 94 | class PermissionsTestCase(Generic.Tests): 95 | name = "permissions" 96 | 97 | @patch( 98 | "requests.sessions.Session.get", 99 | return_value=Response(fixture="users/permission.json"), 100 | ) 101 | def test_username(self, _): 102 | permission = nb.permissions.get(1) 103 | self.assertEqual(len(permission.users), 1) 104 | user = permission.users[0] 105 | self.assertEqual(str(user), "user1") 106 | 107 | @patch( 108 | "requests.sessions.Session.get", 109 | return_value=Response(fixture="users/permission.json"), 110 | ) 111 | def test_constraints(self, _): 112 | permission = nb.permissions.get(1) 113 | self.assertIsInstance(permission.constraints, list) 114 | self.assertIsInstance(permission.constraints[0], dict) 115 | 116 | 117 | class UnknownModelTestCase(unittest.TestCase): 118 | """This test validates that an unknown model is returned as Record object 119 | and that the __str__() method correctly uses the 'display' field of the 120 | object (introduced as a standard field in NetBox 2.11.0). 121 | """ 122 | 123 | @patch( 124 | "requests.sessions.Session.get", 125 | return_value=Response(fixture="users/unknown_model.json"), 126 | ) 127 | def test_unknown_model(self, _): 128 | unknown_obj = nb.unknown_model.get(1) 129 | self.assertEqual(type(unknown_obj), pynetbox.core.response.Record) 130 | self.assertEqual(str(unknown_obj), "Unknown object") 131 | -------------------------------------------------------------------------------- /tests/test_virtualization.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | import pynetbox 5 | 6 | from .util import Response 7 | 8 | api = pynetbox.api( 9 | "http://localhost:8000", 10 | ) 11 | 12 | nb = api.virtualization 13 | 14 | HEADERS = {"accept": "application/json"} 15 | 16 | 17 | class Generic: 18 | class Tests(unittest.TestCase): 19 | name = "" 20 | ret = pynetbox.core.response.Record 21 | app = "virtualization" 22 | 23 | def test_get_all(self): 24 | with patch( 25 | "requests.sessions.Session.get", 26 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 27 | ) as mock: 28 | ret = list(getattr(nb, self.name).all()) 29 | self.assertTrue(ret) 30 | self.assertTrue(isinstance(ret[0], self.ret)) 31 | mock.assert_called_with( 32 | "http://localhost:8000/api/{}/{}/".format( 33 | self.app, self.name.replace("_", "-") 34 | ), 35 | params={"limit": 0}, 36 | json=None, 37 | headers=HEADERS, 38 | ) 39 | 40 | def test_filter(self): 41 | with patch( 42 | "requests.sessions.Session.get", 43 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 44 | ) as mock: 45 | ret = list(getattr(nb, self.name).filter(name="test")) 46 | self.assertTrue(ret) 47 | self.assertTrue(isinstance(ret[0], self.ret)) 48 | mock.assert_called_with( 49 | "http://localhost:8000/api/{}/{}/".format( 50 | self.app, self.name.replace("_", "-") 51 | ), 52 | params={"name": "test", "limit": 0}, 53 | json=None, 54 | headers=HEADERS, 55 | ) 56 | 57 | def test_get(self): 58 | with patch( 59 | "requests.sessions.Session.get", 60 | return_value=Response( 61 | fixture="{}/{}.json".format(self.app, self.name[:-1]) 62 | ), 63 | ) as mock: 64 | ret = getattr(nb, self.name).get(1) 65 | self.assertTrue(ret) 66 | self.assertTrue(isinstance(ret, self.ret)) 67 | mock.assert_called_with( 68 | "http://localhost:8000/api/{}/{}/1/".format( 69 | self.app, self.name.replace("_", "-") 70 | ), 71 | params={}, 72 | json=None, 73 | headers=HEADERS, 74 | ) 75 | 76 | 77 | class ClusterTypesTestCase(Generic.Tests): 78 | name = "cluster_types" 79 | 80 | 81 | class ClusterGroupsTestCase(Generic.Tests): 82 | name = "cluster_groups" 83 | 84 | 85 | class ClustersTestCase(Generic.Tests): 86 | name = "clusters" 87 | 88 | 89 | class VirtualMachinesTestCase(Generic.Tests): 90 | name = "virtual_machines" 91 | 92 | 93 | class InterfacesTestCase(Generic.Tests): 94 | name = "interfaces" 95 | -------------------------------------------------------------------------------- /tests/test_wireless.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | import pynetbox 5 | 6 | from .util import Response 7 | 8 | api = pynetbox.api("http://localhost:8000") 9 | 10 | nb_app = api.wireless 11 | 12 | HEADERS = {"accept": "application/json"} 13 | 14 | 15 | class Generic: 16 | class Tests(unittest.TestCase): 17 | name = "" 18 | ret = pynetbox.core.response.Record 19 | app = "wireless" 20 | 21 | def test_get_all(self): 22 | with patch( 23 | "requests.sessions.Session.get", 24 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 25 | ) as mock: 26 | ret = list(getattr(nb_app, self.name).all()) 27 | self.assertTrue(ret) 28 | self.assertTrue(isinstance(ret[0], self.ret)) 29 | mock.assert_called_with( 30 | "http://localhost:8000/api/{}/{}/".format( 31 | self.app, self.name.replace("_", "-") 32 | ), 33 | params={"limit": 0}, 34 | json=None, 35 | headers=HEADERS, 36 | ) 37 | 38 | def test_filter(self): 39 | with patch( 40 | "requests.sessions.Session.get", 41 | return_value=Response(fixture="{}/{}.json".format(self.app, self.name)), 42 | ) as mock: 43 | ret = list(getattr(nb_app, self.name).filter(name="test")) 44 | self.assertTrue(ret) 45 | self.assertTrue(isinstance(ret[0], self.ret)) 46 | mock.assert_called_with( 47 | "http://localhost:8000/api/{}/{}/".format( 48 | self.app, self.name.replace("_", "-") 49 | ), 50 | params={"name": "test", "limit": 0}, 51 | json=None, 52 | headers=HEADERS, 53 | ) 54 | 55 | def test_get(self): 56 | with patch( 57 | "requests.sessions.Session.get", 58 | return_value=Response( 59 | fixture="{}/{}.json".format(self.app, self.name[:-1]) 60 | ), 61 | ) as mock: 62 | ret = getattr(nb_app, self.name).get(1) 63 | self.assertTrue(ret) 64 | self.assertTrue(isinstance(ret, self.ret)) 65 | mock.assert_called_with( 66 | "http://localhost:8000/api/{}/{}/1/".format( 67 | self.app, self.name.replace("_", "-") 68 | ), 69 | params={}, 70 | json=None, 71 | headers=HEADERS, 72 | ) 73 | 74 | 75 | class WirelessLansTestCase(Generic.Tests): 76 | name = "wireless_lans" 77 | 78 | @patch( 79 | "requests.sessions.Session.get", 80 | return_value=Response(fixture="wireless/wireless_lan.json"), 81 | ) 82 | def test_repr(self, _): 83 | wireless_lan_obj = nb_app.wireless_lans.get(1) 84 | self.assertEqual(type(wireless_lan_obj), pynetbox.models.wireless.WirelessLans) 85 | self.assertEqual(str(wireless_lan_obj), "SSID 1") 86 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/pynetbox/c159a765fe8c56cdf3a1e276357ebd60867b0cea/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_detailendpoint.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | import pynetbox 5 | 6 | nb = pynetbox.api("http://localhost:8000") 7 | 8 | 9 | class DetailEndpointTestCase(unittest.TestCase): 10 | def test_detail_endpoint_create_single(self): 11 | # Prefixes 12 | with patch( 13 | "pynetbox.core.query.Request._make_call", 14 | return_value={"id": 123, "prefix": "1.2.3.0/24"}, 15 | ): 16 | prefix_obj = nb.ipam.prefixes.get(123) 17 | self.assertEqual(prefix_obj.prefix, "1.2.3.0/24") 18 | with patch( 19 | "pynetbox.core.query.Request._make_call", 20 | return_value={"address": "1.2.3.1/24"}, 21 | ): 22 | ip_obj = prefix_obj.available_ips.create() 23 | self.assertEqual(ip_obj.address, "1.2.3.1/24") 24 | # IP Ranges 25 | with patch( 26 | "pynetbox.core.query.Request._make_call", 27 | return_value={"id": 321, "display": "1.2.4.1-254/24"}, 28 | ): 29 | ip_range_obj = nb.ipam.ip_ranges.get(321) 30 | self.assertEqual(ip_range_obj.display, "1.2.4.1-254/24") 31 | with patch( 32 | "pynetbox.core.query.Request._make_call", 33 | return_value={"address": "1.2.4.2/24"}, 34 | ): 35 | ip_obj = ip_range_obj.available_ips.create() 36 | self.assertEqual(ip_obj.address, "1.2.4.2/24") 37 | 38 | def test_detail_endpoint_create_list(self): 39 | # Prefixes 40 | with patch( 41 | "pynetbox.core.query.Request._make_call", 42 | return_value={"id": 123, "prefix": "1.2.3.0/24"}, 43 | ): 44 | prefix_obj = nb.ipam.prefixes.get(123) 45 | self.assertEqual(prefix_obj.prefix, "1.2.3.0/24") 46 | with patch( 47 | "pynetbox.core.query.Request._make_call", 48 | return_value=[{"address": "1.2.3.1/24"}, {"address": "1.2.3.2/24"}], 49 | ): 50 | ip_list = prefix_obj.available_ips.create([{} for _ in range(2)]) 51 | self.assertTrue(isinstance(ip_list, list)) 52 | self.assertEqual(len(ip_list), 2) 53 | # IP Ranges 54 | with patch( 55 | "pynetbox.core.query.Request._make_call", 56 | return_value={"id": 321, "display": "1.2.4.1-254/24"}, 57 | ): 58 | ip_range_obj = nb.ipam.ip_ranges.get(321) 59 | self.assertEqual(ip_range_obj.display, "1.2.4.1-254/24") 60 | with patch( 61 | "pynetbox.core.query.Request._make_call", 62 | return_value=[{"address": "1.2.4.2/24"}, {"address": "1.2.4.3/24"}], 63 | ): 64 | ip_list = ip_range_obj.available_ips.create([{} for _ in range(2)]) 65 | self.assertTrue(isinstance(ip_list, list)) 66 | self.assertEqual(len(ip_list), 2) 67 | -------------------------------------------------------------------------------- /tests/unit/test_extras.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pynetbox.models.extras import ConfigContexts 4 | 5 | 6 | class ExtrasTestCase(unittest.TestCase): 7 | def test_config_contexts(self): 8 | test_values = { 9 | "data": { 10 | "test_int": 123, 11 | "test_str": "testing", 12 | "test_list": [1, 2, 3], 13 | } 14 | } 15 | test = ConfigContexts(test_values, None, None) 16 | self.assertTrue(test) 17 | 18 | def test_config_contexts_diff_str(self): 19 | test_values = { 20 | "data": { 21 | "test_int": 123, 22 | "test_str": "testing", 23 | "test_list": [1, 2, 3], 24 | "test_dict": {"foo": "bar"}, 25 | } 26 | } 27 | test = ConfigContexts(test_values, None, None) 28 | test.data["test_str"] = "bar" 29 | self.assertEqual(test._diff(), {"data"}) 30 | 31 | def test_config_contexts_diff_dict(self): 32 | test_values = { 33 | "data": { 34 | "test_int": 123, 35 | "test_str": "testing", 36 | "test_list": [1, 2, 3], 37 | "test_dict": {"foo": "bar"}, 38 | } 39 | } 40 | test = ConfigContexts(test_values, None, None) 41 | test.data["test_dict"].update({"bar": "foo"}) 42 | self.assertEqual(test._diff(), {"data"}) 43 | -------------------------------------------------------------------------------- /tests/unit/test_query.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import Mock, call 3 | 4 | from pynetbox.core.query import Request 5 | 6 | 7 | class RequestTestCase(unittest.TestCase): 8 | def test_get_count(self): 9 | test_obj = Request( 10 | http_session=Mock(), 11 | base="http://localhost:8001/api/dcim/devices", 12 | filters={"q": "abcd"}, 13 | ) 14 | test_obj.http_session.get.return_value.json.return_value = { 15 | "count": 42, 16 | "next": "http://localhost:8001/api/dcim/devices?limit=1&offset=1&q=abcd", 17 | "previous": False, 18 | "results": [], 19 | } 20 | test_obj.http_session.get.ok = True 21 | test = test_obj.get_count() 22 | self.assertEqual(test, 42) 23 | test_obj.http_session.get.assert_called_with( 24 | "http://localhost:8001/api/dcim/devices/", 25 | params={"q": "abcd", "limit": 1, "brief": 1}, 26 | headers={"accept": "application/json"}, 27 | json=None, 28 | ) 29 | 30 | def test_get_count_no_filters(self): 31 | test_obj = Request( 32 | http_session=Mock(), 33 | base="http://localhost:8001/api/dcim/devices", 34 | ) 35 | test_obj.http_session.get.return_value.json.return_value = { 36 | "count": 42, 37 | "next": "http://localhost:8001/api/dcim/devices?limit=1&offset=1", 38 | "previous": False, 39 | "results": [], 40 | } 41 | test_obj.http_session.get.ok = True 42 | test = test_obj.get_count() 43 | self.assertEqual(test, 42) 44 | test_obj.http_session.get.assert_called_with( 45 | "http://localhost:8001/api/dcim/devices/", 46 | params={"limit": 1, "brief": 1}, 47 | headers={"accept": "application/json"}, 48 | json=None, 49 | ) 50 | 51 | def test_get_manual_pagination(self): 52 | test_obj = Request( 53 | http_session=Mock(), 54 | base="http://localhost:8001/api/dcim/devices", 55 | limit=10, 56 | offset=20, 57 | ) 58 | test_obj.http_session.get.return_value.json.return_value = { 59 | "count": 4, 60 | "next": "http://localhost:8001/api/dcim/devices?limit=10&offset=30", 61 | "previous": False, 62 | "results": [1, 2, 3, 4], 63 | } 64 | expected = call( 65 | "http://localhost:8001/api/dcim/devices/", 66 | params={"offset": 20, "limit": 10}, 67 | headers={"accept": "application/json"}, 68 | ) 69 | test_obj.http_session.get.ok = True 70 | generator = test_obj.get() 71 | self.assertEqual(len(list(generator)), 4) 72 | test_obj.http_session.get.assert_called_with( 73 | "http://localhost:8001/api/dcim/devices/", 74 | params={"offset": 20, "limit": 10}, 75 | headers={"accept": "application/json"}, 76 | json=None, 77 | ) 78 | -------------------------------------------------------------------------------- /tests/unit/test_request.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import Mock 3 | 4 | from pynetbox.core.query import Request 5 | 6 | 7 | class RequestTestCase(unittest.TestCase): 8 | def test_get_openapi_version_less_than_3_5(self): 9 | test = Request("http://localhost:8080/api", Mock()) 10 | test.get_version = Mock(return_value="3.4") 11 | 12 | # Mock the HTTP response 13 | response_mock = Mock() 14 | response_mock.ok = True 15 | test.http_session.get.return_value = response_mock 16 | 17 | test.get_openapi() 18 | test.http_session.get.assert_called_with( 19 | "http://localhost:8080/api/docs/?format=openapi", 20 | headers={"Accept": "application/json", "Content-Type": "application/json"}, 21 | ) 22 | 23 | def test_get_openapi_version_3_5_or_greater(self): 24 | test = Request("http://localhost:8080/api", Mock()) 25 | test.get_version = Mock(return_value="3.5") 26 | 27 | # Mock the HTTP response 28 | response_mock = Mock() 29 | response_mock.ok = True 30 | test.http_session.get.return_value = response_mock 31 | 32 | test.get_openapi() 33 | test.http_session.get.assert_called_with( 34 | "http://localhost:8080/api/schema/", 35 | headers={"Accept": "application/json", "Content-Type": "application/json"}, 36 | ) 37 | -------------------------------------------------------------------------------- /tests/util.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class Response: 5 | def __init__(self, fixture=None, status_code=200, ok=True, content=None): 6 | self.status_code = status_code 7 | self.content = json.dumps(content) if content else self.load_fixture(fixture) 8 | self.ok = ok 9 | 10 | def load_fixture(self, path): 11 | if not path: 12 | return "{}" 13 | with open("tests/fixtures/{}".format(path), "r") as f: 14 | return f.read() 15 | 16 | def json(self): 17 | return json.loads(self.content) 18 | --------------------------------------------------------------------------------