diff --git a/.github/workflows/Pynetbox.yaml b/.github/workflows/Pynetbox.yaml new file mode 100644 index 00000000..84f45a5d --- /dev/null +++ b/.github/workflows/Pynetbox.yaml @@ -0,0 +1,37 @@ +name: Pynetbox Data Uploader Tests + +on: + push: + branches: + - master + pull_request: + paths: + - "Pynetbox_Data_Uploader/**" + - ".github/workflows/Pynetbox.yaml" + +jobs: + test_and_lint: + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: ["3.x"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r Pynetbox_Data_Uploader/requirements.txt + + - name: Run tests + run: cd Pynetbox_Data_Uploader && python3 -m pytest . + + - name: Analyse with pylint + run: | + cd Pynetbox_Data_Uploader && pylint $(git ls-files '*.py') --rcfile=.pylintrc + diff --git a/.gitignore b/.gitignore index d39e7248..ac7b7a49 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ venv/ .coverage coverage.xml -Netbox_CSV_Read/DataFiles \ No newline at end of file diff --git a/Pynetbox_Data_Uploader/.pylintrc b/Pynetbox_Data_Uploader/.pylintrc new file mode 100644 index 00000000..6fa198d4 --- /dev/null +++ b/Pynetbox_Data_Uploader/.pylintrc @@ -0,0 +1,14 @@ +[FORMAT] +# Black will enforce 88 chars on Python code +# this will enforce 120 chars on docs / comments +max-line-length=120 + +# Disable various warnings: +# C0114: Missing module string - we don't need module strings for the small repo +# C0115: Missing class doc string - a lot of the actions are self explanatory +# W0511: TODOs they're well....to do later +# R0801: Similar lines - Imports and method signatures will flag this, such as forwarding action args +# C0116: Missing method docstring - Adds too much noise +# E0401: Ignore import errors as imports work + +disable=C0114,C0115,E0401,W0511,R0801,C0116,E0401 \ No newline at end of file diff --git a/Pynetbox_Data_Uploader/lib/__init__.py b/Pynetbox_Data_Uploader/lib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Pynetbox_Data_Uploader/lib/enums/__init__.py b/Pynetbox_Data_Uploader/lib/enums/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Netbox_CSV_Read/Enums/dcim_device_id.py b/Pynetbox_Data_Uploader/lib/enums/dcim_device_id.py similarity index 99% rename from Netbox_CSV_Read/Enums/dcim_device_id.py rename to Pynetbox_Data_Uploader/lib/enums/dcim_device_id.py index 84624c2c..5877fcf0 100644 --- a/Netbox_CSV_Read/Enums/dcim_device_id.py +++ b/Pynetbox_Data_Uploader/lib/enums/dcim_device_id.py @@ -5,6 +5,7 @@ class DeviceInfoID(Enum): """ This Enums Class stores enums that are used to retrieve data from Netbox. """ + DEVICE_ROLE = "dcim.device_roles" DESCRIPTION = "dcim.devices" DEVICE_TYPE = "dcim.device_types" diff --git a/Netbox_CSV_Read/Enums/dcim_device_no_id.py b/Pynetbox_Data_Uploader/lib/enums/dcim_device_no_id.py similarity index 52% rename from Netbox_CSV_Read/Enums/dcim_device_no_id.py rename to Pynetbox_Data_Uploader/lib/enums/dcim_device_no_id.py index 66b0c2e8..21759889 100644 --- a/Netbox_CSV_Read/Enums/dcim_device_no_id.py +++ b/Pynetbox_Data_Uploader/lib/enums/dcim_device_no_id.py @@ -5,9 +5,10 @@ class DeviceInfoNoID(Enum): """ This Enums Class stores enums that are used to retrieve data from Netbox. """ - position = "position" - name = "name" - serial = "serial" - airflow = "airflow" - status = "status" - face = "face" \ No newline at end of file + + POSITION = "position" + NAME = "name" + SERIAL = "serial" + AIRFLOW = "airflow" + STATUS = "status" + FACE = "face" diff --git a/Pynetbox_Data_Uploader/lib/netbox_api/__init__.py b/Pynetbox_Data_Uploader/lib/netbox_api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Netbox_CSV_Read/CSV/format_dict.py b/Pynetbox_Data_Uploader/lib/netbox_api/format_dict.py similarity index 88% rename from Netbox_CSV_Read/CSV/format_dict.py rename to Pynetbox_Data_Uploader/lib/netbox_api/format_dict.py index 04b0655c..f1ac1e24 100644 --- a/Netbox_CSV_Read/CSV/format_dict.py +++ b/Pynetbox_Data_Uploader/lib/netbox_api/format_dict.py @@ -1,14 +1,15 @@ from typing import Dict, List -from Enums.dcim_device_id import DeviceInfoID -from Enums.dcim_device_no_id import DeviceInfoNoID -from Netbox_Api.netbox_connect import NetboxConnect -from Netbox_Api.netbox_data import NetboxGetID +from enums.dcim_device_id import DeviceInfoID +from enums.dcim_device_no_id import DeviceInfoNoID +from netbox_api.netbox_connect import NetboxConnect +from netbox_api.netbox_data import NetboxGetID class FormatDict(NetboxConnect): """ This class takes dictionaries with string values and changes those to ID values from Netbox. """ + def __init__(self, dicts: list): """ This method initialises the class with the following parameters. diff --git a/Netbox_CSV_Read/Netbox_Api/netbox_connect.py b/Pynetbox_Data_Uploader/lib/netbox_api/netbox_connect.py similarity index 93% rename from Netbox_CSV_Read/Netbox_Api/netbox_connect.py rename to Pynetbox_Data_Uploader/lib/netbox_api/netbox_connect.py index f57554a0..5ad46feb 100644 --- a/Netbox_CSV_Read/Netbox_Api/netbox_connect.py +++ b/Pynetbox_Data_Uploader/lib/netbox_api/netbox_connect.py @@ -1,5 +1,7 @@ import pynetbox as nb +# pylint:disable = too-few-public-methods + class NetboxConnect: """ diff --git a/Netbox_CSV_Read/Netbox_Api/netbox_create.py b/Pynetbox_Data_Uploader/lib/netbox_api/netbox_create.py similarity index 90% rename from Netbox_CSV_Read/Netbox_Api/netbox_create.py rename to Pynetbox_Data_Uploader/lib/netbox_api/netbox_create.py index fe16efe1..fea180a8 100644 --- a/Netbox_CSV_Read/Netbox_Api/netbox_create.py +++ b/Pynetbox_Data_Uploader/lib/netbox_api/netbox_create.py @@ -1,5 +1,5 @@ from typing import Optional -from Netbox_Api.netbox_connect import NetboxConnect +from netbox_api.netbox_connect import NetboxConnect class NetboxDCIM: @@ -23,7 +23,7 @@ def create_device(self, data: dict | list) -> bool: return bool(devices) def create_device_type( - self, model: str, slug: str, manufacturer: str, u_height=1 + self, model: str, slug: str, manufacturer: str, u_height: int = 1 ) -> bool: """ This method creates a new device type in Netbox. diff --git a/Netbox_CSV_Read/Netbox_Api/netbox_data.py b/Pynetbox_Data_Uploader/lib/netbox_api/netbox_data.py similarity index 75% rename from Netbox_CSV_Read/Netbox_Api/netbox_data.py rename to Pynetbox_Data_Uploader/lib/netbox_api/netbox_data.py index 579b9bba..c318e780 100644 --- a/Netbox_CSV_Read/Netbox_Api/netbox_data.py +++ b/Pynetbox_Data_Uploader/lib/netbox_api/netbox_data.py @@ -1,14 +1,17 @@ -from Netbox_Api.netbox_connect import NetboxConnect -from Enums.dcim_device_id import DeviceInfoID -from Enums.dcim_device_no_id import DeviceInfoNoID from operator import attrgetter -from typing import Optional +from typing import Optional, Union +from netbox_api.netbox_connect import NetboxConnect +from enums.dcim_device_id import DeviceInfoID +from enums.dcim_device_no_id import DeviceInfoNoID + +# pylint:disable = too-few-public-methods class NetboxGetID(NetboxConnect): """ This class retrieves field value ID's from Netbox. """ + def __init__(self, url: str, token: str, api: Optional = None): """ This method initialises the class with the following parameters. @@ -23,21 +26,23 @@ def __init__(self, url: str, token: str, api: Optional = None): self.enums_id = DeviceInfoID self.enums_no_id = DeviceInfoNoID - def get_id(self, attr_string: str, netbox_value: str, site_value: str) -> int | str: + def get_id( + self, attr_string: str, netbox_value: str, site_value: str + ) -> Union[int, str]: """ - This method uses the Pynetbox Api .get() method to retrieve the ID of a string value from Netbox. + This method uses Pynetbox Api .get() to retrieve the ID of a string value from Netbox. :param attr_string: The attribute string to get. :param netbox_value: The value to search for in Netbox. :param site_value: The value of the site key in the dictionary :return: Returns the value/ID """ attr_string = attr_string.upper() - attr_to_look_for = getattr(self.enums_id, attr_string).value # Gets Enums value + attr_to_look_for = getattr(self.enums_id, attr_string).value # Gets enums value value = attrgetter(attr_to_look_for)(self.netbox) # Gets netbox attr if attr_string == "DEVICE_TYPE": value = value.get(slug=netbox_value).id elif attr_string == "LOCATION": - if type(site_value) == int: + if isinstance(site_value, int): 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) diff --git a/Netbox_CSV_Read/Netbox_Api/netbox_existence.py b/Pynetbox_Data_Uploader/lib/netbox_api/netbox_existence.py similarity index 91% rename from Netbox_CSV_Read/Netbox_Api/netbox_existence.py rename to Pynetbox_Data_Uploader/lib/netbox_api/netbox_existence.py index ce3aad5c..f7c2fe62 100644 --- a/Netbox_CSV_Read/Netbox_Api/netbox_existence.py +++ b/Pynetbox_Data_Uploader/lib/netbox_api/netbox_existence.py @@ -1,5 +1,5 @@ from typing import Optional -from Netbox_Api.netbox_connect import NetboxConnect +from netbox_api.netbox_connect import NetboxConnect class NetboxExistence: @@ -29,4 +29,4 @@ def check_device_type_exists(self, device_type: str) -> bool: :return: Returns bool. """ device_type = self.netbox.dcim.device_types.get(slug=device_type) - return bool(device_type) \ No newline at end of file + return bool(device_type) diff --git a/Pynetbox_Data_Uploader/lib/utils/__init__.py b/Pynetbox_Data_Uploader/lib/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Netbox_CSV_Read/CSV/csv_utils.py b/Pynetbox_Data_Uploader/lib/utils/csv_to_dict.py similarity index 59% rename from Netbox_CSV_Read/CSV/csv_utils.py rename to Pynetbox_Data_Uploader/lib/utils/csv_to_dict.py index cc105ba0..1cbd41c3 100644 --- a/Netbox_CSV_Read/CSV/csv_utils.py +++ b/Pynetbox_Data_Uploader/lib/utils/csv_to_dict.py @@ -1,30 +1,30 @@ -import pandas from typing import List, Dict +import pandas as pd class CsvUtils: """ - This class provides methods to read data from CSV files and allow the data to be easily read and used elsewhere. + This class provides methods to read data from csv files + and allow the data to be easily read and used elsewhere. """ - def __init__(self): - self.pd = pandas - def csv_to_python(self, file_path: str) -> Dict: + @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 CSV file to be read from. + :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 = self.pd.read_csv(file_path) + dataframe = pd.read_csv(file_path) dataframe = dataframe.to_dict(orient="list") return dataframe @staticmethod def separate_data(data: dict) -> List: """ - This method reduces Pandas CSV to Dict conversion to individual dictionaries. - :param data: The data from the CSV file - :return: Returns a list of dictionaries which each represent a row of data from CSV. + 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]]) diff --git a/Pynetbox_Data_Uploader/pytest.ini b/Pynetbox_Data_Uploader/pytest.ini new file mode 100644 index 00000000..65c35ff4 --- /dev/null +++ b/Pynetbox_Data_Uploader/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +pythonpath = lib +testpaths = Tests +python_files = *.py +python_functions = test_* \ No newline at end of file diff --git a/Netbox_CSV_Read/requirements.txt b/Pynetbox_Data_Uploader/requirements.txt similarity index 81% rename from Netbox_CSV_Read/requirements.txt rename to Pynetbox_Data_Uploader/requirements.txt index be73a76d..a12310d5 100644 Binary files a/Netbox_CSV_Read/requirements.txt and b/Pynetbox_Data_Uploader/requirements.txt differ diff --git a/Pynetbox_Data_Uploader/tests/__init__.py b/Pynetbox_Data_Uploader/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Netbox_CSV_Read/Tests/test_csv_utils.py b/Pynetbox_Data_Uploader/tests/test_csv_utils.py similarity index 61% rename from Netbox_CSV_Read/Tests/test_csv_utils.py rename to Pynetbox_Data_Uploader/tests/test_csv_utils.py index 54cd1c37..9605e6a2 100644 --- a/Netbox_CSV_Read/Tests/test_csv_utils.py +++ b/Pynetbox_Data_Uploader/tests/test_csv_utils.py @@ -1,5 +1,5 @@ -from unittest.mock import MagicMock, NonCallableMock -from CSV.csv_utils import CsvUtils +from unittest.mock import NonCallableMock, patch +from utils.csv_to_dict import CsvUtils import pytest @@ -12,10 +12,9 @@ def test_csv_to_python(instance): """ This test ensures that the csv_read method is called once with the file_path arg. """ - mock_dataframe = MagicMock() file_path = NonCallableMock() - instance.pd = mock_dataframe - instance.csv_to_python(file_path) + with patch("utils.csv_utils.pd") as mock_dataframe: + instance.csv_to_python(file_path) mock_dataframe.read_csv.assert_called_once_with(file_path) @@ -24,7 +23,9 @@ def test_separate_data(instance): This test ensures that the dictionaries from panda formatted into row by row dictionaries. These are much more understandable and can be used individually or in bulk. """ - test_data = {"key1": ["Adata1", "Bdata1"], - "key2": ["Adata2", "Bdata2"]} + test_data = {"key1": ["Adata1", "Bdata1"], "key2": ["Adata2", "Bdata2"]} format_data = instance.separate_data(test_data) - assert format_data == [{"key1": "Adata1", "key2": "Adata2"}, {"key1": "Bdata1", "key2": "Bdata2"}] + assert format_data == [ + {"key1": "Adata1", "key2": "Adata2"}, + {"key1": "Bdata1", "key2": "Bdata2"}, + ] diff --git a/Netbox_CSV_Read/Tests/test_netbox_connect.py b/Pynetbox_Data_Uploader/tests/test_netbox_connect.py similarity index 71% rename from Netbox_CSV_Read/Tests/test_netbox_connect.py rename to Pynetbox_Data_Uploader/tests/test_netbox_connect.py index b1caaa60..1c703ccb 100644 --- a/Netbox_CSV_Read/Tests/test_netbox_connect.py +++ b/Pynetbox_Data_Uploader/tests/test_netbox_connect.py @@ -1,5 +1,5 @@ -from unittest.mock import MagicMock, NonCallableMock, patch -from Netbox_Api.netbox_connect import NetboxConnect +from unittest.mock import NonCallableMock, patch +from netbox_api.netbox_connect import NetboxConnect import pytest @@ -14,7 +14,7 @@ def test_api_object(instance): """ This test checks that the Api method is called once. """ - with patch("Netbox_Api.netbox_connect.nb") as mock_netbox: + with patch("netbox_api.netbox_connect.nb") as mock_netbox: res = instance.api_object() mock_netbox.api.assert_called_once_with(instance.url, instance.token) assert res == mock_netbox.api.return_value diff --git a/Netbox_CSV_Read/Tests/test_netbox_create.py b/Pynetbox_Data_Uploader/tests/test_netbox_create.py similarity index 96% rename from Netbox_CSV_Read/Tests/test_netbox_create.py rename to Pynetbox_Data_Uploader/tests/test_netbox_create.py index fa69d008..e8a19fe5 100644 --- a/Netbox_CSV_Read/Tests/test_netbox_create.py +++ b/Pynetbox_Data_Uploader/tests/test_netbox_create.py @@ -1,5 +1,5 @@ from unittest.mock import MagicMock, NonCallableMock -from Netbox_Api.netbox_create import NetboxDCIM +from netbox_api.netbox_create import NetboxDCIM import pytest diff --git a/Netbox_CSV_Read/Tests/test_netbox_existence.py b/Pynetbox_Data_Uploader/tests/test_netbox_existence.py similarity index 94% rename from Netbox_CSV_Read/Tests/test_netbox_existence.py rename to Pynetbox_Data_Uploader/tests/test_netbox_existence.py index 5dd5aece..b59fed17 100644 --- a/Netbox_CSV_Read/Tests/test_netbox_existence.py +++ b/Pynetbox_Data_Uploader/tests/test_netbox_existence.py @@ -1,5 +1,5 @@ from unittest.mock import MagicMock, NonCallableMock -from Netbox_Api.netbox_existence import NetboxExistence +from netbox_api.netbox_existence import NetboxExistence import pytest @@ -34,4 +34,4 @@ def test_check_device_type_exists(instance): device_type = NonCallableMock() instance.netbox.dcim.device_types = mock_device_types instance.check_device_type_exists(device_type) - mock_device_types.get.assert_called_once_with(slug=device_type) \ No newline at end of file + mock_device_types.get.assert_called_once_with(slug=device_type)