diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 42916ecf5..4f834576e 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v7 diff --git a/gns3server/agent/web_wireshark/docker_client.py b/gns3server/agent/web_wireshark/docker_client.py index 00f62edd2..e030eab35 100644 --- a/gns3server/agent/web_wireshark/docker_client.py +++ b/gns3server/agent/web_wireshark/docker_client.py @@ -23,16 +23,11 @@ This module handles Docker API operations via aiohttp Unix socket. import asyncio import logging -import sys +from asyncio import timeout as async_timeout from typing import Any import aiohttp -if sys.version_info >= (3, 11): - from asyncio import timeout as async_timeout -else: - from async_timeout import timeout as async_timeout - from gns3server.utils import parse_version logger = logging.getLogger(__name__) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index 033ef96d9..13458aef4 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -671,11 +671,7 @@ class BaseNode: ) # keep forwarding websocket data in both direction - if sys.version_info >= (3, 11, 0): - # Starting with Python 3.11, passing coroutine objects to wait() directly is forbidden. - aws = [asyncio.create_task(ws_forward(telnet_writer)), asyncio.create_task(telnet_forward(telnet_reader))] - else: - aws = [ws_forward(telnet_writer), telnet_forward(telnet_reader)] + aws = [asyncio.create_task(ws_forward(telnet_writer)), asyncio.create_task(telnet_forward(telnet_reader))] done, pending = await asyncio.wait(aws, return_when=asyncio.FIRST_COMPLETED) for task in done: @@ -762,11 +758,7 @@ class BaseNode: log.warning(f"Exception while forwarding VNC data to WebSocket: {e!r}") # Keep forwarding WebSocket data in both directions - if sys.version_info >= (3, 11, 0): - # Starting with Python 3.11, passing coroutine objects to wait() directly is forbidden. - aws = [asyncio.create_task(ws_forward(vnc_writer)), asyncio.create_task(vnc_forward(vnc_reader))] - else: - aws = [ws_forward(vnc_writer), vnc_forward(vnc_reader)] + aws = [asyncio.create_task(ws_forward(vnc_writer)), asyncio.create_task(vnc_forward(vnc_reader))] done, pending = await asyncio.wait(aws, return_when=asyncio.FIRST_COMPLETED) for task in done: diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index 1b3b65e38..fd136eb91 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -19,19 +19,14 @@ import asyncio import io import ipaddress import json +import logging import socket import sys +from asyncio import timeout as asynctimeout import aiohttp from fastapi import HTTPException -if sys.version_info >= (3, 11): - from asyncio import timeout as asynctimeout -else: - from async_timeout import timeout as asynctimeout - -import logging - from ..controller.controller_error import ( ComputeConflictError, ComputeError, diff --git a/gns3server/db/repositories/rbac.py b/gns3server/db/repositories/rbac.py index 4417e901d..93cdbe32a 100644 --- a/gns3server/db/repositories/rbac.py +++ b/gns3server/db/repositories/rbac.py @@ -16,11 +16,11 @@ # along with this program. If not, see . import logging -from typing import List, Optional, Union, cast +from typing import Any, List, Optional, Union, cast from urllib.parse import urlparse from uuid import UUID -from sqlalchemy import delete, select, update +from sqlalchemy import Select, delete, select, update from sqlalchemy.engine import CursorResult from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -331,7 +331,7 @@ class RbacRepository(BaseRepository): Retrieve all user ACEs matching the user_id and privilege name. """ - query = ( + query: Select[Any, Any, Any, Any] = ( select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name) .join(models.Privilege.roles) .join(models.Role.acl_entries) @@ -349,7 +349,7 @@ class RbacRepository(BaseRepository): Retrieve all group ACEs matching the user_id and privilege name. """ - query = ( + query: Select[Any, Any, Any, Any] = ( select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name) .join(models.Privilege.roles) .join(models.Role.acl_entries) diff --git a/gns3server/server.py b/gns3server/server.py index 3ec16e456..099696f55 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -255,9 +255,9 @@ class Server: else: log.info(f"Compute authentication is enabled with username '{config.Server.compute_username}'") - # we only support Python 3 version >= 3.9 - if sys.version_info < (3, 9, 0): - raise SystemExit("Python 3.9 or higher is required") + # we only support Python 3 version >= 3.11 + if sys.version_info < (3, 11, 0): + raise SystemExit("Python 3.11 or higher is required") log.info( f"Running with Python {sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]} and has PID {os.getpid()}" diff --git a/pyproject.toml b/pyproject.toml index 6cba871da..c5b62ecd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta" [project] name = "gns3-server" -description = "GNS3 graphical interface for the GNS3 server." +description = "GNS3 server (controller and compute) for GNS3 network simulator" license-files = ["LICENSE"] authors = [ { name = "Jeremy Grossmann", email = "developers@gns3.com" } ] readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.11" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -21,10 +21,10 @@ classifiers = [ "Natural Language :: English", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython" ] diff --git a/requirements.txt b/requirements.txt index 2094cab64..c874bfbb4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,17 @@ # GNS3 Server Core Dependencies uvicorn==0.54.0 pydantic==2.13.5 -fastapi==0.141.1 +fastapi==0.142.4 python-multipart==0.0.32 -websockets==16.1.1 # version 16.1.1 is the last version that supports Python 3.10 -aiohttp>=3.14.3,<3.15 +websockets==16.1.1 # google-genai 2.10.0 has requirement websockets<17.0 +aiohttp>=3.14.4,<3.15 aiofiles>=25.1.0,<26.0 Jinja2>=3.1.6,<3.2 sentry-sdk>=2.70.0,<3 # optional dependency psutil>=7.2.2 -async-timeout>=5.0.1,<5.1; python_version < '3.11' # this library has effectively been upstreamed into Python 3.11+ distro>=1.9.0 py-cpuinfo>=9.0.0,<10.0 -greenlet==3.5.3; python_version >= '3.13' # necessary to run sqlalchemy on Python >= 3.13 -sqlalchemy==2.0.54 # version 2.0.54 is the last version that supports Python 3.10 +sqlalchemy[asyncio]==2.1.4 aiosqlite==0.22.1 alembic==1.20 bcrypt==5.0.0 @@ -22,7 +20,7 @@ email-validator==2.3.0 watchdog==6.0.0 zstandard==0.25.0 platformdirs>=2.4.0 # fastmcp-slim >=3.4 requires >=4.0.0; upper bound removed for compatibility -truststore>=0.10.4; python_version >= '3.10' +truststore>=0.10.4 # Shared dependencies (also used by AI Copilot) telnetlib3==5.0.1,<6