Merge pull request #2965 from GNS3/drop-python-3.10

Drop Python 3.10 support
This commit is contained in:
Jeremy Grossmann 2026-10-08 19:01:34 +02:00 committed by GitHub
commit 24634b8f0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 21 additions and 41 deletions

View File

@ -17,7 +17,7 @@ jobs:
strategy: strategy:
matrix: matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] python-version: ["3.11", "3.12", "3.13", "3.14"]
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7

View File

@ -23,16 +23,11 @@ This module handles Docker API operations via aiohttp Unix socket.
import asyncio import asyncio
import logging import logging
import sys from asyncio import timeout as async_timeout
from typing import Any from typing import Any
import aiohttp 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 from gns3server.utils import parse_version
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -671,11 +671,7 @@ class BaseNode:
) )
# keep forwarding websocket data in both direction # keep forwarding websocket data in both direction
if sys.version_info >= (3, 11, 0): aws = [asyncio.create_task(ws_forward(telnet_writer)), asyncio.create_task(telnet_forward(telnet_reader))]
# 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)]
done, pending = await asyncio.wait(aws, return_when=asyncio.FIRST_COMPLETED) done, pending = await asyncio.wait(aws, return_when=asyncio.FIRST_COMPLETED)
for task in done: for task in done:
@ -762,11 +758,7 @@ class BaseNode:
log.warning(f"Exception while forwarding VNC data to WebSocket: {e!r}") log.warning(f"Exception while forwarding VNC data to WebSocket: {e!r}")
# Keep forwarding WebSocket data in both directions # Keep forwarding WebSocket data in both directions
if sys.version_info >= (3, 11, 0): aws = [asyncio.create_task(ws_forward(vnc_writer)), asyncio.create_task(vnc_forward(vnc_reader))]
# 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)]
done, pending = await asyncio.wait(aws, return_when=asyncio.FIRST_COMPLETED) done, pending = await asyncio.wait(aws, return_when=asyncio.FIRST_COMPLETED)
for task in done: for task in done:

View File

@ -19,19 +19,14 @@ import asyncio
import io import io
import ipaddress import ipaddress
import json import json
import logging
import socket import socket
import sys import sys
from asyncio import timeout as asynctimeout
import aiohttp import aiohttp
from fastapi import HTTPException 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 ( from ..controller.controller_error import (
ComputeConflictError, ComputeConflictError,
ComputeError, ComputeError,

View File

@ -16,11 +16,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging import logging
from typing import List, Optional, Union, cast from typing import Any, List, Optional, Union, cast
from urllib.parse import urlparse from urllib.parse import urlparse
from uuid import UUID from uuid import UUID
from sqlalchemy import delete, select, update from sqlalchemy import Select, delete, select, update
from sqlalchemy.engine import CursorResult from sqlalchemy.engine import CursorResult
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
@ -331,7 +331,7 @@ class RbacRepository(BaseRepository):
Retrieve all user ACEs matching the user_id and privilege name. 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) select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name)
.join(models.Privilege.roles) .join(models.Privilege.roles)
.join(models.Role.acl_entries) .join(models.Role.acl_entries)
@ -349,7 +349,7 @@ class RbacRepository(BaseRepository):
Retrieve all group ACEs matching the user_id and privilege name. 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) select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name)
.join(models.Privilege.roles) .join(models.Privilege.roles)
.join(models.Role.acl_entries) .join(models.Role.acl_entries)

View File

@ -255,9 +255,9 @@ class Server:
else: else:
log.info(f"Compute authentication is enabled with username '{config.Server.compute_username}'") log.info(f"Compute authentication is enabled with username '{config.Server.compute_username}'")
# we only support Python 3 version >= 3.9 # we only support Python 3 version >= 3.11
if sys.version_info < (3, 9, 0): if sys.version_info < (3, 11, 0):
raise SystemExit("Python 3.9 or higher is required") raise SystemExit("Python 3.11 or higher is required")
log.info( log.info(
f"Running with Python {sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]} and has PID {os.getpid()}" f"Running with Python {sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]} and has PID {os.getpid()}"

View File

@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "gns3-server" name = "gns3-server"
description = "GNS3 graphical interface for the GNS3 server." description = "GNS3 server (controller and compute) for GNS3 network simulator"
license-files = ["LICENSE"] license-files = ["LICENSE"]
authors = [ authors = [
{ name = "Jeremy Grossmann", email = "developers@gns3.com" } { name = "Jeremy Grossmann", email = "developers@gns3.com" }
] ]
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.11"
classifiers = [ classifiers = [
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Environment :: Console", "Environment :: Console",
@ -21,10 +21,10 @@ classifiers = [
"Natural Language :: English", "Natural Language :: English",
"Operating System :: POSIX :: Linux", "Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython" "Programming Language :: Python :: Implementation :: CPython"
] ]

View File

@ -1,19 +1,17 @@
# GNS3 Server Core Dependencies # GNS3 Server Core Dependencies
uvicorn==0.54.0 uvicorn==0.54.0
pydantic==2.13.5 pydantic==2.13.5
fastapi==0.141.1 fastapi==0.142.4
python-multipart==0.0.32 python-multipart==0.0.32
websockets==16.1.1 # version 16.1.1 is the last version that supports Python 3.10 websockets==16.1.1 # google-genai 2.10.0 has requirement websockets<17.0
aiohttp>=3.14.3,<3.15 aiohttp>=3.14.4,<3.15
aiofiles>=25.1.0,<26.0 aiofiles>=25.1.0,<26.0
Jinja2>=3.1.6,<3.2 Jinja2>=3.1.6,<3.2
sentry-sdk>=2.70.0,<3 # optional dependency sentry-sdk>=2.70.0,<3 # optional dependency
psutil>=7.2.2 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 distro>=1.9.0
py-cpuinfo>=9.0.0,<10.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[asyncio]==2.1.4
sqlalchemy==2.0.54 # version 2.0.54 is the last version that supports Python 3.10
aiosqlite==0.22.1 aiosqlite==0.22.1
alembic==1.20 alembic==1.20
bcrypt==5.0.0 bcrypt==5.0.0
@ -22,7 +20,7 @@ email-validator==2.3.0
watchdog==6.0.0 watchdog==6.0.0
zstandard==0.25.0 zstandard==0.25.0
platformdirs>=2.4.0 # fastmcp-slim >=3.4 requires >=4.0.0; upper bound removed for compatibility 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) # Shared dependencies (also used by AI Copilot)
telnetlib3==5.0.1,<6 telnetlib3==5.0.1,<6