diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fe82c2a..9250755 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,11 @@ All notable changes to the ``topology`` project will be documented in this file. [UNRELEASED] - Under development ******************************** +Changed +======= +- Updated python environment installation from 3.9 to 3.11 +- Updated test dependencies + [2023.2.0] - 2024-02-16 *********************** diff --git a/README.rst b/README.rst index f325dc6..0060916 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,10 @@ To install this NApp, first, make sure to have the same venv activated as you ha $ git clone https://github.com/kytos-ng/topology.git $ cd topology - $ python setup.py develop + $ python3 -m pip install --editable . + +To install the kytos environment, please follow our +`development environment setup `_. Requirements ============ diff --git a/controllers/__init__.py b/controllers/__init__.py index 48c43e4..16e674e 100644 --- a/controllers/__init__.py +++ b/controllers/__init__.py @@ -113,7 +113,7 @@ def upsert_switch(self, dpid: str, switch_dict: dict) -> Optional[dict]: updated = self.db.switches.find_one_and_update( {"_id": dpid}, { - "$set": model.dict(exclude={"inserted_at"}), + "$set": model.model_dump(exclude={"inserted_at"}), "$setOnInsert": {"inserted_at": utc_now}, }, return_document=ReturnDocument.AFTER, @@ -208,7 +208,7 @@ def upsert_link(self, link_id: str, link_dict: dict) -> dict: updated = self.db.links.find_one_and_update( {"_id": link_id}, { - "$set": model.dict(exclude={"inserted_at"}), + "$set": model.model_dump(exclude={"inserted_at"}), "$setOnInsert": {"inserted_at": utc_now}, }, return_document=ReturnDocument.AFTER, @@ -311,7 +311,7 @@ def upsert_interface_details( "special_available_tags": special_available_tags, "special_tags": special_tags, "updated_at": utc_now - }).dict(exclude={"inserted_at"}) + }).model_dump(exclude={"inserted_at"}) updated = self.db.interface_details.find_one_and_update( {"_id": id_}, { diff --git a/db/models/__init__.py b/db/models/__init__.py index f127231..575c90f 100644 --- a/db/models/__init__.py +++ b/db/models/__init__.py @@ -3,21 +3,22 @@ # pylint: disable=no-name-in-module from datetime import datetime -from typing import Dict, List, Optional +from typing import Dict, Optional -from pydantic import BaseModel, Field, conlist, validator +from pydantic import BaseModel, Field, field_validator +from typing_extensions import Annotated class DocumentBaseModel(BaseModel): """DocumentBaseModel.""" id: str = Field(None, alias="_id") - inserted_at: Optional[datetime] - updated_at: Optional[datetime] + inserted_at: Optional[datetime] = None + updated_at: Optional[datetime] = None - def dict(self, **kwargs) -> dict: + def model_dump(self, **kwargs) -> dict: """Model to dict.""" - values = super().dict(**kwargs) + values = super().model_dump(**kwargs) if "id" in values and values["id"]: values["_id"] = values["id"] if "exclude" in kwargs and "_id" in kwargs["exclude"]: @@ -37,28 +38,28 @@ class InterfaceSubDoc(BaseModel): nni: bool = False lldp: bool switch: str - link: Optional[str] - link_side: Optional[str] + link: Optional[str] = None + link_side: Optional[str] = None metadata: dict = {} - updated_at: Optional[datetime] + updated_at: Optional[datetime] = None class SwitchDoc(DocumentBaseModel): """Switch DB Document Model.""" enabled: bool - data_path: Optional[str] - hardware: Optional[str] - manufacturer: Optional[str] - software: Optional[str] - connection: Optional[str] - ofp_version: Optional[str] - serial: Optional[str] + data_path: Optional[str] = None + hardware: Optional[str] = None + manufacturer: Optional[str] = None + software: Optional[str] = None + connection: Optional[str] = None + ofp_version: Optional[str] = None + serial: Optional[str] = None metadata: dict = {} - interfaces: List[InterfaceSubDoc] = [] + interfaces: list[InterfaceSubDoc] = [] - @validator("interfaces", pre=True) - def preset_interfaces(cls, v, values, **kwargs) -> List[InterfaceSubDoc]: + @field_validator("interfaces", mode="before") + def preset_interfaces(cls, v, values, **kwargs) -> list[InterfaceSubDoc]: """Preset interfaces.""" if isinstance(v, dict): return list(v.values()) @@ -104,7 +105,8 @@ class LinkDoc(DocumentBaseModel): enabled: bool metadata: dict = {} - endpoints: conlist(InterfaceIdSubDoc, min_items=2, max_items=2) + endpoints: Annotated[list[InterfaceIdSubDoc], + Field(min_length=2, max_length=2)] @staticmethod def projection() -> dict: @@ -124,7 +126,7 @@ def projection() -> dict: class InterfaceDetailDoc(DocumentBaseModel): """InterfaceDetail DB Document Model.""" - available_tags: Dict[str, List[List[int]]] - tag_ranges: Dict[str, List[List[int]]] - special_available_tags: Dict[str, List[str]] - special_tags: Dict[str, List[str]] + available_tags: Dict[str, list[list[int]]] + tag_ranges: Dict[str, list[list[int]]] + special_available_tags: Dict[str, list[str]] + special_tags: Dict[str, list[str]] diff --git a/setup.py b/setup.py index f00f97f..4069004 100644 --- a/setup.py +++ b/setup.py @@ -130,7 +130,12 @@ class Linter(SimpleCommand): def run(self): """Run yala.""" print('Yala is running. It may take several seconds...') - check_call('yala *.py controllers db tests', shell=True) + try: + check_call('yala *.py controllers db tests', shell=True) + print('No linter error found.') + except CalledProcessError: + print('Linter check failed. Fix the error(s) above and try again.') + sys.exit(-1) class KytosInstall: diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index e349151..0722d0b 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -23,7 +23,8 @@ def get_switch_mock(of_version, connection_state=ConnectionState.NEW, address = Mock() port = Mock() socket = Mock() - switch.connection = Connection(address, port, socket) + transport = Mock() + switch.connection = Connection(address, port, socket, transport) switch.connection.protocol.version = of_version switch.connection.state = connection_state return switch diff --git a/tests/unit/test_db_models.py b/tests/unit/test_db_models.py index 12a30e9..caf3a01 100644 --- a/tests/unit/test_db_models.py +++ b/tests/unit/test_db_models.py @@ -14,8 +14,8 @@ def test_document_base_model_dict() -> None: utcnow = datetime.utcnow() payload = {"_id": _id, "inserted_at": utcnow, "updated_at": utcnow} model = DocumentBaseModel(**payload) - assert model.dict() == {**payload, **{"id": _id}} - assert "_id" not in model.dict(exclude={"_id"}) + assert model.model_dump() == {**payload, **{"id": _id}} + assert "_id" not in model.model_dump(exclude={"_id"}) def test_switch_doc_preset_interfaces() -> None: diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index c16612a..289e83b 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -1,6 +1,7 @@ """Module to test the main napp file.""" # pylint: disable=import-error,no-name-in-module,wrong-import-order # pylint: disable=import-outside-toplevel,attribute-defined-outside-init +import asyncio import pytest import time from datetime import timedelta @@ -648,10 +649,10 @@ async def test_get_switch_metadata(self): @patch('napps.kytos.topology.main.Main.notify_metadata_changes') async def test_add_switch_metadata( - self, mock_metadata_changes, event_loop + self, mock_metadata_changes ): """Test add_switch_metadata.""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() dpid = "00:00:00:00:00:00:00:01" mock_switch = get_switch_mock(dpid) self.napp.controller.switches = {dpid: mock_switch} @@ -672,9 +673,9 @@ async def test_add_switch_metadata( response = await self.api_client.post(endpoint, json=payload) assert response.status_code == 404 - async def test_add_switch_metadata_wrong_format(self, event_loop): + async def test_add_switch_metadata_wrong_format(self): """Test add_switch_metadata_wrong_format.""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() dpid = "00:00:00:00:00:00:00:01" payload = 'A' @@ -688,10 +689,10 @@ async def test_add_switch_metadata_wrong_format(self, event_loop): @patch('napps.kytos.topology.main.Main.notify_metadata_changes') async def test_delete_switch_metadata( - self, mock_metadata_changes, event_loop + self, mock_metadata_changes ): """Test delete_switch_metadata.""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() dpid = "00:00:00:00:00:00:00:01" mock_switch = get_switch_mock(dpid) mock_switch.metadata = {"A": "A"} @@ -890,10 +891,10 @@ async def test_get_interface_metadata(self): @patch('napps.kytos.topology.main.Main.notify_metadata_changes') async def test_add_interface_metadata( - self, mock_metadata_changes, event_loop + self, mock_metadata_changes ): """Test add_interface_metadata.""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' dpid = '00:00:00:00:00:00:00:01' mock_switch = get_switch_mock(dpid) @@ -919,9 +920,9 @@ async def test_add_interface_metadata( response = await self.api_client.post(endpoint, json=payload) assert response.status_code == 404 - async def test_add_interface_metadata_wrong_format(self, event_loop): + async def test_add_interface_metadata_wrong_format(self): """Test add_interface_metadata_wrong_format.""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = "00:00:00:00:00:00:00:01:1" endpoint = f"{self.base_endpoint}/interfaces/{interface_id}/metadata" response = await self.api_client.post(endpoint, json='A') @@ -929,9 +930,9 @@ async def test_add_interface_metadata_wrong_format(self, event_loop): response = await self.api_client.post(endpoint, json=None) assert response.status_code == 415 - async def test_delete_interface_metadata(self, event_loop): + async def test_delete_interface_metadata(self): """Test delete_interface_metadata.""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' dpid = '00:00:00:00:00:00:00:01' mock_switch = get_switch_mock(dpid) @@ -1089,11 +1090,10 @@ async def test_get_link_metadata(self): async def test_add_link_metadata( self, mock_metadata_changes, - mock_topology_update, - event_loop + mock_topology_update ): """Test add_link_metadata.""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() mock_link = MagicMock(Link) mock_link.metadata = "A" self.napp.links = {'1': mock_link} @@ -1112,9 +1112,9 @@ async def test_add_link_metadata( response = await self.api_client.post(endpoint, json=payload) assert response.status_code == 404 - async def test_add_link_metadata_wrong_format(self, event_loop): + async def test_add_link_metadata_wrong_format(self): """Test add_link_metadata_wrong_format.""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() link_id = 'cf0f4071be426b3f745027f5d22' payload = "A" endpoint = f"{self.base_endpoint}/links/{link_id}/metadata" @@ -1724,9 +1724,9 @@ def test_interruption_end( assert mock_notify_link_status_change.call_count == 2 mock_notify_topology_update.assert_called_once() - async def test_set_tag_range(self, event_loop): + async def test_set_tag_range(self): """Test set_tag_range""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' dpid = '00:00:00:00:00:00:00:01' mock_switch = get_switch_mock(dpid) @@ -1748,9 +1748,9 @@ async def test_set_tag_range(self, event_loop): assert args[1] == payload['tag_type'] assert self.napp.handle_on_interface_tags.call_count == 1 - async def test_set_tag_range_not_found(self, event_loop): + async def test_set_tag_range_not_found(self): """Test set_tag_range. Not found""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' self.napp.controller.get_interface_by_id = MagicMock() self.napp.controller.get_interface_by_id.return_value = None @@ -1762,9 +1762,9 @@ async def test_set_tag_range_not_found(self, event_loop): response = await self.api_client.post(url, json=payload) assert response.status_code == 404 - async def test_set_tag_range_tag_error(self, event_loop): + async def test_set_tag_range_tag_error(self): """Test set_tag_range TagRangeError""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' dpid = '00:00:00:00:00:00:00:01' mock_switch = get_switch_mock(dpid) @@ -1783,9 +1783,9 @@ async def test_set_tag_range_tag_error(self, event_loop): assert response.status_code == 400 assert mock_interface.notify_interface_tags.call_count == 0 - async def test_set_tag_range_type_error(self, event_loop): + async def test_set_tag_range_type_error(self): """Test set_tag_range TagRangeError""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' dpid = '00:00:00:00:00:00:00:01' mock_switch = get_switch_mock(dpid) @@ -1806,9 +1806,9 @@ async def test_set_tag_range_type_error(self, event_loop): assert response.status_code == 400 assert self.napp.handle_on_interface_tags.call_count == 0 - async def test_delete_tag_range(self, event_loop): + async def test_delete_tag_range(self): """Test delete_tag_range""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' dpid = '00:00:00:00:00:00:00:01' mock_switch = get_switch_mock(dpid) @@ -1822,9 +1822,9 @@ async def test_delete_tag_range(self, event_loop): assert response.status_code == 200 assert mock_interface.remove_tag_ranges.call_count == 1 - async def test_delete_tag_range_not_found(self, event_loop): + async def test_delete_tag_range_not_found(self): """Test delete_tag_range. Not found""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' dpid = '00:00:00:00:00:00:00:01' mock_switch = get_switch_mock(dpid) @@ -1837,9 +1837,9 @@ async def test_delete_tag_range_not_found(self, event_loop): assert response.status_code == 404 assert mock_interface.remove_tag_ranges.call_count == 0 - async def test_delete_tag_range_type_error(self, event_loop): + async def test_delete_tag_range_type_error(self): """Test delete_tag_range TagRangeError""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' dpid = '00:00:00:00:00:00:00:01' mock_switch = get_switch_mock(dpid) @@ -1853,9 +1853,9 @@ async def test_delete_tag_range_type_error(self, event_loop): response = await self.api_client.delete(url) assert response.status_code == 400 - async def test_get_all_tag_ranges(self, event_loop): + async def test_get_all_tag_ranges(self): """Test get_all_tag_ranges""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() dpid = '00:00:00:00:00:00:00:01' switch = get_switch_mock(dpid) interface = get_interface_mock('s1-eth1', 1, switch) @@ -1878,9 +1878,9 @@ async def test_get_all_tag_ranges(self, event_loop): assert response.status_code == 200 assert response.json() == expected - async def test_get_tag_ranges_by_intf(self, event_loop): + async def test_get_tag_ranges_by_intf(self): """Test get_tag_ranges_by_intf""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() dpid = '00:00:00:00:00:00:00:01' switch = get_switch_mock(dpid) interface = get_interface_mock('s1-eth1', 1, switch) @@ -1905,9 +1905,9 @@ async def test_get_tag_ranges_by_intf(self, event_loop): assert response.status_code == 200 assert response.json() == expected - async def test_get_tag_ranges_by_intf_error(self, event_loop): + async def test_get_tag_ranges_by_intf_error(self): """Test get_tag_ranges_by_intf with NotFound""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() dpid = '00:00:00:00:00:00:00:01' self.napp.controller.get_interface_by_id = MagicMock() self.napp.controller.get_interface_by_id.return_value = None @@ -1915,9 +1915,9 @@ async def test_get_tag_ranges_by_intf_error(self, event_loop): response = await self.api_client.get(url) assert response.status_code == 404 - async def test_set_special_tags(self, event_loop): + async def test_set_special_tags(self): """Test set_special_tags""" - self.napp.controller.loop = event_loop + self.napp.controller.loop = asyncio.get_running_loop() interface_id = '00:00:00:00:00:00:00:01:1' dpid = '00:00:00:00:00:00:00:01' mock_switch = get_switch_mock(dpid) diff --git a/tox.ini b/tox.ini index 87adfb5..2caee15 100644 --- a/tox.ini +++ b/tox.ini @@ -1,22 +1,22 @@ [tox] -envlist = py39,coverage,lint +envlist = coverage,lint [testenv] -whitelist_externals = rm -deps = -Urrequirements/dev.in +allowlist_externals = rm +deps = -rrequirements/dev.in setenv= - PYTHONPATH = {toxworkdir}/py39/var/lib/kytos/:{envdir} + PYTHONPATH = {toxworkdir}/py311/var/lib/kytos/:{envdir} [testenv:coverage] skip_install = true -envdir = {toxworkdir}/py39 +envdir = {toxworkdir}/py311 commands= python3 setup.py coverage {posargs} [testenv:lint] skip_install = true -envdir = {toxworkdir}/py39 +envdir = {toxworkdir}/py311 commands = python3 setup.py lint