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

chore: Upgraded python and dependencies #460

Merged
merged 24 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.9'
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.9]
python-version: [3.11]

steps:
- uses: actions/checkout@v1
Expand Down
2 changes: 1 addition & 1 deletion .scrutinizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ checks:
duplicate_code: true
build:
environment:
python: 3.9.12
python: 3.11.8
postgresql: false
redis: false
dependencies:
Expand Down
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ python:
- "3.7"
- "3.8"
- "3.9"
- "3.11"
install:
- pip install --upgrade pip
- pip install -r requirements/dev.txt
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ All notable changes to the kytos 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
***********************

Expand Down
3 changes: 1 addition & 2 deletions kytos/core/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,7 @@ def _get_decorated_functions(napp):
and isinstance(pub_attr.route_index, int)
):
callables.append(pub_attr)
for pub_attr in sorted(callables, key=lambda f: f.route_index):
yield pub_attr
yield from sorted(callables, key=lambda f: f.route_index)

@classmethod
def get_absolute_rule(cls, rule, napp):
Expand Down
5 changes: 2 additions & 3 deletions kytos/core/atcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def __init__(self):
if not self.server:
raise ValueError("server instance must be assigned before init")

def connection_made(self, transport):
def connection_made(self, transport: asyncio.Transport):
"""Handle new client connection, passing it to the controller.

Build a new Kytos `Connection` and send a ``kytos/core.connection.new``
Expand All @@ -126,11 +126,10 @@ def connection_made(self, transport):

addr, port = transport.get_extra_info('peername')
_, server_port = transport.get_extra_info('sockname')
socket = transport.get_extra_info('socket')

LOG.info("New connection from %s:%s", addr, port)

self.connection = Connection(addr, port, socket)
self.connection = Connection(addr, port, transport)

# This allows someone to inherit from KytosServer and start a server
# on another port to handle a different protocol.
Expand Down
8 changes: 4 additions & 4 deletions kytos/core/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
content_type_json_or_415, error_msg,
get_json_or_400)
from kytos.core.retry import before_sleep, for_all_methods, retries
from kytos.core.user import HashSubDoc, UserDoc, UserDocUpdate
from kytos.core.user import HashSubDoc, UserDoc, UserDocUpdate, hashing

__all__ = ['authenticated']

Expand Down Expand Up @@ -110,7 +110,7 @@ def create_user(self, user_data: dict) -> InsertOneResult:
"email": user_data.get('email'),
"inserted_at": utc_now,
"updated_at": utc_now,
}).dict())
}).model_dump())
except DuplicateKeyError as err:
raise err
except ValidationError as err:
Expand Down Expand Up @@ -142,7 +142,7 @@ def update_user(self, username: str, data: dict) -> dict:
"$set": UserDocUpdate(**{
**data,
**{"updated_at": utc_now}
}).dict(exclude_none=True)
}).model_dump(exclude_none=True)
},
return_document=ReturnDocument.AFTER
)
Expand Down Expand Up @@ -307,7 +307,7 @@ def _authenticate_user(self, request: Request) -> JSONResponse:
user = self._find_user(username)
if user["state"] != 'active':
raise HTTPException(401, detail='This user is not active')
password_hashed = UserDoc.hashing(password, user["hash"])
password_hashed = hashing(password, user["hash"])
if user["password"] != password_hashed:
raise HTTPException(401, detail="Incorrect password")
time_exp = datetime.datetime.utcnow() + datetime.timedelta(
Expand Down
30 changes: 18 additions & 12 deletions kytos/core/connection.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
"""Module with main classes related to Connections."""
import logging
from asyncio import Transport
from enum import Enum
from errno import EBADF, ENOTCONN
from socket import SHUT_RDWR
from socket import error as SocketError

__all__ = ('Connection', 'ConnectionProtocol', 'ConnectionState')

Expand Down Expand Up @@ -33,19 +32,26 @@ def __init__(self, name=None, version=None, state=None):
class Connection:
"""Connection class to abstract a network connections."""

def __init__(self, address, port, socket, switch=None):
def __init__(
self,
address: str,
port: int,
transport: Transport,
switch=None
):
"""Assign parameters to instance variables.

Args:
address (|hw_address|): Source address.
port (int): Port number.
socket (socket): socket.
socket (TransportSocket): socket.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

socket docstring become obsolete, feel free to nuke it

Alopalao marked this conversation as resolved.
Show resolved Hide resolved
transport (Transport): transport.
switch (:class:`~.Switch`): switch with this connection.
"""
self.address = address
self.port = port
self.socket = socket
self.switch = switch
self.transport = transport
self.state = ConnectionState.NEW
self.protocol = ConnectionProtocol()
self.remaining_data = b''
Expand All @@ -55,7 +61,7 @@ def __str__(self):

def __repr__(self):
return f"Connection({self.address!r}, {self.port!r}," + \
f" {self.socket!r}, {self.switch!r}, {self.state!r})"
f" {self.transport!r}, {self.switch!r}, {self.state!r})"

@property
def state(self):
Expand All @@ -65,6 +71,7 @@ def state(self):
@state.setter
def state(self, new_state):
if new_state not in ConnectionState:
# pylint: disable=broad-exception-raised
raise Exception('Unknown State', new_state)
# pylint: disable=attribute-defined-outside-init
self._state = new_state
Expand All @@ -90,8 +97,8 @@ def send(self, buffer):
"""
try:
if self.is_alive():
self.socket.sendall(buffer)
except (OSError, SocketError) as exception:
viniarck marked this conversation as resolved.
Show resolved Hide resolved
self.transport.write(buffer)
except OSError as exception:
LOG.debug('Could not send packet. Exception: %s', exception)
self.close()
raise
Expand All @@ -102,9 +109,8 @@ def close(self):
LOG.debug('Shutting down Connection %s', self.id)

try:
self.socket.shutdown(SHUT_RDWR)
self.socket.close()
self.socket = None
self.transport.close()
viniarck marked this conversation as resolved.
Show resolved Hide resolved
self.transport = None
LOG.debug('Connection Closed: %s', self.id)
except OSError as exception:
if exception.errno not in (ENOTCONN, EBADF):
Expand All @@ -114,7 +120,7 @@ def close(self):

def is_alive(self):
"""Return True if the connection socket is alive. False otherwise."""
return self.socket is not None and self.state not in (
return self.transport is not None and self.state not in (
ConnectionState.FINISHED, ConnectionState.FAILED)

def is_new(self):
Expand Down
15 changes: 10 additions & 5 deletions kytos/core/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from importlib import reload as reload_module
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
from socket import error as SocketError

from pyof.foundation.exceptions import PackException

Expand All @@ -38,7 +37,7 @@
from kytos.core.auth import Auth
from kytos.core.buffers import KytosBuffers
from kytos.core.config import KytosConfig
from kytos.core.connection import ConnectionState
from kytos.core.connection import Connection, ConnectionState
from kytos.core.db import db_conn_wait
from kytos.core.dead_letter import DeadLetter
from kytos.core.events import KytosEvent
Expand Down Expand Up @@ -107,7 +106,7 @@ def __init__(self, options=None, loop: AbstractEventLoop = None):
#: This dict stores all connections between the controller and the
#: switches. The key for this dict is a tuple (ip, port). The content
#: is a Connection
self.connections = {}
self.connections: dict[tuple, Connection] = {}
#: dict: mapping of events and event listeners.
#:
#: The key of the dict is a KytosEvent (or a string that represent a
Expand Down Expand Up @@ -155,6 +154,9 @@ def __init__(self, options=None, loop: AbstractEventLoop = None):
self._alisten_tasks = set()
self.qmonitors: list[QueueMonitorWindow] = []

#: APM client in memory to be closed when necessary
self.apm = None

self._register_endpoints()
#: Adding the napps 'enabled' directory into the PATH
#: Now you can access the enabled napps with:
Expand Down Expand Up @@ -259,7 +261,7 @@ def start(self, restart=False):
db_conn_wait(db_backend=self.options.database)
self.start_auth()
if self.options.apm:
init_apm(self.options.apm, app=self.api_server.app)
self.apm = init_apm(self.options.apm, app=self.api_server.app)
if not restart:
self.create_pidfile()
self.start_controller()
Expand Down Expand Up @@ -509,6 +511,9 @@ def stop_controller(self, graceful=True):
# self.server.server_close()

self.stop_queue_monitors()
if self.apm:
self.log.info("Stopping APM server...")
self.apm.close()
Alopalao marked this conversation as resolved.
Show resolved Hide resolved
self.log.info("Stopping API Server...")
self.api_server.stop()
self.log.info("Stopped API Server")
Expand Down Expand Up @@ -626,7 +631,7 @@ async def msg_out_event_handler(self):
message.header.xid,
packet.hex())
self.notify_listeners(triggered_event)
except (OSError, SocketError):
except OSError:
await self.publish_connection_error(triggered_event)
self.log.info("connection closed. Cannot send message")
except PackException as err:
Expand Down
4 changes: 2 additions & 2 deletions kytos/core/dead_letter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import List

# pylint: disable=no-name-in-module,invalid-name
from pydantic import BaseModel, ValidationError, constr
from pydantic import BaseModel, Field, ValidationError

# pylint: enable=no-name-in-module
from kytos.core.rest_api import (HTTPException, JSONResponse, Request,
Expand All @@ -22,7 +22,7 @@ class KytosQueueBufferNames(str, Enum):
class DeadLetterDeletePayload(BaseModel):
"""DeadLetterDeletePayload."""

event_name: constr(min_length=1)
event_name: str = Field(min_length=1)
ids: List[str] = []


Expand Down
66 changes: 27 additions & 39 deletions kytos/core/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@
from pathlib import Path
from threading import Thread

from openapi_core.spec import Spec
from openapi_core.spec.shortcuts import create_spec
from openapi_core.validation.request import openapi_request_validator
from openapi_core.validation.request.datatypes import RequestValidationResult
from openapi_core import Spec, unmarshal_request
from openapi_core.exceptions import OpenAPIError
from openapi_spec_validator import validate_spec
from openapi_spec_validator.readers import read_from_filename

Expand Down Expand Up @@ -181,7 +179,7 @@ def handler_context_apm(*args, apm_client=None):
handler_func, kwargs = handler_context, {}
if get_apm_name() == "es":
handler_func = handler_context_apm
kwargs = dict(apm_client=ElasticAPM.get_client())
kwargs = {"apm_client": ElasticAPM.get_client()}

def get_executor(pool, event, default_pool="app", handler=handler):
"""Get executor."""
Expand Down Expand Up @@ -271,7 +269,7 @@ async def handler_context_apm(*args, apm_client=None):
handler_func, kwargs = handler_context, {}
if get_apm_name() == "es":
handler_func = handler_context_apm
kwargs = dict(apm_client=ElasticAPM.get_client())
kwargs = {"apm_client": ElasticAPM.get_client()}

async def inner(*args):
"""Inner decorated with events attribute."""
Expand Down Expand Up @@ -359,36 +357,22 @@ def _read_from_filename(yml_file_path: Path) -> dict:

def load_spec(yml_file_path: Path):
"""Load and validate spec object given a yml file path."""
spec_dict = _read_from_filename(yml_file_path)
validate_spec(spec_dict)
return create_spec(spec_dict)


def _request_validation_result_or_400(result: RequestValidationResult) -> None:
"""Request validation result or raise HTTP 400."""
if not result.errors:
return
error_response = (
"The request body contains invalid API data."
)
errors = result.errors[0]
if hasattr(errors, "schema_errors"):
schema_errors = errors.schema_errors[0]
error_log = {
"error_message": schema_errors.message,
"error_validator": schema_errors.validator,
"error_validator_value": schema_errors.validator_value,
"error_path": list(schema_errors.path),
"error_schema": schema_errors.schema,
"error_schema_path": list(schema_errors.schema_path),
}
LOG.debug(f"Invalid request (API schema): {error_log}")
error_response += f" {schema_errors.message} for field"
error_response += (
f" {'/'.join(map(str,schema_errors.path))}."
)
else:
spec = _read_from_filename(yml_file_path)
validate_spec(spec)
return Spec.from_dict(spec)


def _request_validation_result_or_400(errors: OpenAPIError) -> None:
"""Raise HTTP 400."""
error_response = "The request body contains invalid API data."
if not errors.__cause__:
error_response = str(errors)
elif (hasattr(errors.__cause__, "schema_errors") and
errors.__cause__.schema_errors):
schema_errors = errors.__cause__.schema_errors
for error in schema_errors:
error_response += f", {error.message} for field"
error_response += f" {'/'.join(map(str,error.path))}."
raise HTTPException(400, detail=error_response)


Expand All @@ -405,8 +389,10 @@ def validate_openapi_request(
if body:
content_type_json_or_415(request)
openapi_request = StarletteOpenAPIRequest(request, body)
result = openapi_request_validator.validate(spec, openapi_request)
_request_validation_result_or_400(result)
try:
unmarshal_request(openapi_request, spec)
viniarck marked this conversation as resolved.
Show resolved Hide resolved
except OpenAPIError as err:
_request_validation_result_or_400(err)
return body


Expand All @@ -430,8 +416,10 @@ async def avalidate_openapi_request(
if body:
content_type_json_or_415(request)
openapi_request = AStarletteOpenAPIRequest(request, body)
result = openapi_request_validator.validate(spec, openapi_request)
_request_validation_result_or_400(result)
try:
unmarshal_request(openapi_request, spec)
except OpenAPIError as err:
_request_validation_result_or_400(err)
return body


Expand Down
Loading
Loading