Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created Argparse user method. #106

Merged
merged 26 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5516f39
initial argparse commit.
khalford Oct 17, 2023
2adf199
ENH: Constructed user method to create netbox devies from csv in CLI
khalford Oct 17, 2023
d429668
BUG: Made changes for code to work.
khalford Oct 17, 2023
4b2b052
Added a setup.py file to be able to pip install the package.
khalford Oct 17, 2023
908219c
MAINT: Ideal structure to contain the pynetbox logic
khalford Oct 17, 2023
bff7891
MAINT: Moved pynetbox logic into a class.
khalford Oct 17, 2023
2966a25
MAINT: Renamed Classes to init with api rather than netbox.
khalford Oct 17, 2023
8e56485
MAINT: Created 2 functions to organise code.
khalford Oct 17, 2023
db57d5f
MAINT: Black
khalford Oct 17, 2023
2fe1edf
MAINT: Pylint
khalford Oct 17, 2023
0ee8c49
DOC: Docstrings.
khalford Oct 17, 2023
cb98a53
MAINT: Added argparse to requirements.txt
khalford Oct 17, 2023
34365ae
BUG: Added an ignore of setup.py to pytest.ini.
khalford Oct 17, 2023
613138d
MAINT: Pylint
khalford Oct 17, 2023
767d151
MAINT: Moved file path param to method from __init__
khalford Oct 17, 2023
a952ddf
DOC: Edited .pylintrc
khalford Oct 17, 2023
cb26919
Codecov YAML
khalford Oct 20, 2023
7bfd284
BUG: Formatting dictionaries.
khalford Oct 20, 2023
843de38
MAINT: Code format and pretty prints
khalford Oct 20, 2023
6de6fbf
MAINT: Removed duplicate file from rebase.
khalford Oct 20, 2023
6bebc8e
MAINT: Codecov running tests in project root not Pynetbox directory.
khalford Oct 20, 2023
867817d
MAINT: Running Codecov with the wrong args.
khalford Oct 20, 2023
57be281
MAINT: Removed as unknown use.
khalford Oct 20, 2023
b0be9e6
MAINT: Specified where the Codecov report is to upload.
khalford Oct 20, 2023
f98b8c0
BUG: Attempted fix to generate coverage report.
khalford Oct 20, 2023
3266780
DOC: Missing parameters in Docstrings.
khalford Oct 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/Pynetbox.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ jobs:
run: |
cd Pynetbox_Data_Uploader && pylint $(git ls-files '*.py') --rcfile=.pylintrc

- name: Run tests and collect coverage
run: cd Pynetbox_Data_Uploader && python3 -m pytest --cov-report xml:coverage.xml --cov

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./Pynetbox_Data_Uploader/coverage.xml

3 changes: 1 addition & 2 deletions Pynetbox_Data_Uploader/.pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ max-line-length=120
# C0114: Missing module string - we don't need module strings for the small repo
# W0511: TODOs they're well....to do later


disable=C0114,W0511
disable=C0114,,W0511,
4 changes: 2 additions & 2 deletions Pynetbox_Data_Uploader/lib/netbox_api/netbox_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ class NetboxCheck:
This class contains methods that check if an object exists in Netbox.
"""

def __init__(self, netbox):
self.netbox = netbox
def __init__(self, api):
self.netbox = api

def check_device_exists(self, device_name: str) -> bool:
"""
Expand Down
4 changes: 2 additions & 2 deletions Pynetbox_Data_Uploader/lib/netbox_api/netbox_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ class NetboxCreate:
This class contains methods that will interact create objects in Netbox.
"""

def __init__(self, netbox):
self.netbox = netbox
def __init__(self, api):
self.netbox = api

def create_device(self, data: Union[Dict, List]) -> bool:
"""
Expand Down
9 changes: 6 additions & 3 deletions Pynetbox_Data_Uploader/lib/netbox_api/netbox_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ class NetboxGetID:
This class retrieves field value ID's from Netbox.
"""

def __init__(self, netbox):
def __init__(self, api):
"""
This method allows the Netbox Api Object and Enums to be accessible within the class.
"""
self.netbox = netbox
self.netbox = api
self.enums_id = DeviceInfoID
self.enums_no_id = DeviceInfoNoID

Expand All @@ -39,6 +39,9 @@ def get_id(
site_name = self.netbox.dcim.sites.get(site_value).name
site_slug = site_name.replace(" ", "-").lower()
value = value.get(name=netbox_value, site=site_slug)
list_value = list(value)
list_value = [item for item in list_value if item[0] == "id"]
value = list_value[0][1]
else:
value = value.get(name=netbox_value).id
return value
Expand All @@ -50,7 +53,7 @@ def get_id_from_key(self, key: str, dictionary: Dict) -> Union[str, int]:
:param dictionary: The device dictionary being referenced.
:return: If an ID was needed and found it returns the ID. If an ID was not needed it returns the original value.
"""
if key not in list(self.enums_no_id.__members__):
if key.upper() not in list(self.enums_no_id.__members__):
value = self.get_id(
attr_string=key,
netbox_value=dictionary[key],
Expand Down
115 changes: 115 additions & 0 deletions Pynetbox_Data_Uploader/lib/user_methods/csv_to_netbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from typing import List
import argparse
from lib.netbox_api.netbox_create import NetboxCreate
from lib.netbox_api.netbox_connect import NetboxConnect
from lib.netbox_api.netbox_check import NetboxCheck
from lib.utils.format_dict import FormatDict

# pylint:disable = broad-exception-raised
# Disabled this pylint warning as the exception doesn't catch an error.
# We want it to stop the program if a device already exists in netbox.


class CsvToNetbox:
"""
This class contains organised methods in the 4 step proccess of reading csv's to then uploading to Netbox.
"""
def __init__(self, url: str, token: str):
"""
This initialises the class with the following parameters.
It also allows the rest of the class to access the imported Classes.
:param url: The Netbox url.
:param token: The Netbox auth token.
"""
self.netbox = NetboxConnect(url, token).api_object()
self.format_dict = FormatDict(self.netbox)
self.exist = NetboxCheck(self.netbox)
self.create = NetboxCreate(self.netbox)

def read_csv(self, file_path) -> List:
"""
This method calls the csv_to_python and seperate_data method.
This will take the csv file and return a list of device dictionaries.
khalford marked this conversation as resolved.
Show resolved Hide resolved
:return: Returns a list of devices
"""
print("Reading CSV...")
device_data = self.format_dict.csv_to_python(file_path)
device_list = self.format_dict.separate_data(device_data)
print("Read CSV.")
return device_list

def check_netbox(self, device_list: List) -> bool:
"""
This method calls the check_device_exists and check_device_type_exists method on each device in the list.
:param device_list: A list of devices.
:return: Returns True if the devices don't exist and device types do exist. Raises an Exception otherwise.
"""
print("Checking devices in Netbox...")
for device in device_list:
device_exist = self.exist.check_device_exists(device["name"])
if device_exist:
raise Exception(f'Device {device["name"]} already exists in Netbox.')
type_exist = self.exist.check_device_type_exists(device["device_type"])
if not type_exist:
raise Exception(f'Type {device["device_type"]} does not exist.')
print("Checked devices.")
return True

def convert_data(self, device_list: List) -> List:
"""
This method calls the iterate_dict method.
:param device_list: A list of devices.
:return: Returns the updated list of devices.
"""
print("Formatting data...")
formatted_list = self.format_dict.iterate_dicts(device_list)
print("Formatted data.")
return formatted_list

def send_data(self, device_list: List) -> bool:
"""
This method calls the device create method to create devices in Netbox.
:param device_list: A list of devices.
:return: Returns bool whether the devices where created.
"""
print("Sending data to Netbox...")
devices = self.create.create_device(device_list)
print("Sent data.")
return bool(devices)


def arg_parser():
"""
This function creates a parser object and adds 3 arguments to it.
This allows users to run the python file with arguments. Like a script.
"""
parser = argparse.ArgumentParser(
description="Create devices in Netbox from CSV files.",
usage="python csv_to_netbox.py url token file_path",
)
parser.add_argument("url", help="The Netbox URL.")
parser.add_argument("token", help="Your Netbox Token.")
parser.add_argument("file_path", help="Your file path to csv files.")
return parser.parse_args()


def do_csv_to_netbox(args) -> bool:
"""
This function calls the methods from CsvToNetbox class.
:param args: The arguments from argparse. Supplied when the user runs the file from CLI.
:return: Returns bool if devices where created or not.
"""
class_object = CsvToNetbox(url=args.url, token=args.token)
device_list = class_object.read_csv(args.file_path)
class_object.check_netbox(device_list)
format_list = class_object.convert_data(device_list)
result = class_object.send_data(format_list)
return result


if __name__ == "__main__":
arguments = arg_parser()
if do_csv_to_netbox(arguments):
print("Done.")
else:
print("Uh Oh.")
132 changes: 66 additions & 66 deletions Pynetbox_Data_Uploader/lib/utils/format_dict.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,66 @@
from typing import Dict, List
from pandas import read_csv
from lib.netbox_api.netbox_data import NetboxGetID
class FormatDict:
"""
This class takes dictionaries with string values and changes those to ID values from Netbox.
"""
def __init__(self, netbox):
"""
This method initialises the class with the following parameters.
:param netbox: The Netbox object to pass into NetboxGetID
"""
self.netbox = netbox
def iterate_dicts(self, dicts: list) -> List:
"""
This method iterates through each dictionary and calls a format method on each.
:return: Returns the formatted dictionaries.
"""
new_dicts = []
for dictionary in dicts:
new_dicts.append(self.format_dict(dictionary))
return new_dicts
def format_dict(self, dictionary) -> Dict:
"""
This method iterates through each value in the dictionary.
If the value needs to be converted into a Pynetbox ID it calls the .get() method.
:param dictionary: The dictionary to be formatted
:return: Returns the formatted dictionary
"""
for key in dictionary:
netbox_id = NetboxGetID(self.netbox).get_id_from_key(key=key, dictionary=dictionary)
dictionary[key] = netbox_id
return dictionary
@staticmethod
def csv_to_python(file_path: str) -> Dict:
"""
This method reads data from csv files and writes them to a dictionary.
:param file_path: The file path of the utils file to be read from.
:return: Returns the data from the csv as a dictionary.
"""
dataframe = read_csv(file_path)
return dataframe.to_dict(orient="list")
@staticmethod
def separate_data(data: dict) -> List:
"""
This method reduces Pandas utils to Dict conversion to individual dictionaries.
:param data: The data from the utils file.
:return: Returns a list of dictionaries which each represent a row of data from utils.
"""
data_keys = list(data.keys())
len_rows = len(data[data_keys[0]])
dicts = []
for index in range(len_rows):
new_dict = {}
for key in data_keys:
new_dict.update({key: data[key][index]})
dicts.append(new_dict)
return dicts
from typing import Dict, List
from pandas import read_csv
from lib.netbox_api.netbox_data import NetboxGetID


class FormatDict:
"""
This class takes dictionaries with string values and changes those to ID values from Netbox.
"""

def __init__(self, api):
"""
This method initialises the class with the following parameters.
:param api: The Netbox object to pass into NetboxGetID
"""
self.netbox = api

def iterate_dicts(self, dicts: list) -> List:
"""
This method iterates through each dictionary and calls a format method on each.
khalford marked this conversation as resolved.
Show resolved Hide resolved
:return: Returns the formatted dictionaries.
"""
new_dicts = []
for dictionary in dicts:
new_dicts.append(self.format_dict(dictionary))
return new_dicts

def format_dict(self, dictionary) -> Dict:
"""
This method iterates through each value in the dictionary.
If the value needs to be converted into a Pynetbox ID it calls the .get() method.
:param dictionary: The dictionary to be formatted
:return: Returns the formatted dictionary
"""
for key in dictionary:
netbox_id = NetboxGetID(self.netbox).get_id_from_key(key=key, dictionary=dictionary)
dictionary[key] = netbox_id
return dictionary


@staticmethod
def csv_to_python(file_path: str) -> Dict:
"""
This method reads data from csv files and writes them to a dictionary.
:param file_path: The file path of the utils file to be read from.
:return: Returns the data from the csv as a dictionary.
"""
dataframe = read_csv(file_path)
return dataframe.to_dict(orient="list")

@staticmethod
def separate_data(data: dict) -> List:
"""
This method reduces Pandas utils to Dict conversion to individual dictionaries.
:param data: The data from the utils file.
:return: Returns a list of dictionaries which each represent a row of data from utils.
"""
data_keys = list(data.keys())
len_rows = len(data[data_keys[0]])
dicts = []
for index in range(len_rows):
new_dict = {}
for key in data_keys:
new_dict.update({key: data[key][index]})
dicts.append(new_dict)
return dicts
3 changes: 2 additions & 1 deletion Pynetbox_Data_Uploader/pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
pythonpath = lib
testpaths = Tests
python_files = *.py
python_functions = test_*
python_functions = test_*
addopts = --ignore=setup.py
Binary file modified Pynetbox_Data_Uploader/requirements.txt
Binary file not shown.
20 changes: 20 additions & 0 deletions Pynetbox_Data_Uploader/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from setuptools import setup, find_packages

VERSION = "0.1.0"
DESCRIPTION = "python package for PYNETBOX tools"

LONG_DESCRIPTION = """Python package to interact with Netbox from cli."""

setup(
name="Pynetbox_Data_Uploader",
version=VERSION,
author="Kalibh Halford",
author_email="<[email protected]>",
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
packages=find_packages(),
package_dir={"Pynetbox_Data_Uploader": "lib"},
python_requires=">=3.9",
install_requires=[],
keywords=["python"],
)