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

Core: purge py3.8 and py3.9 #3973

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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/pyright-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"reportMissingImports": true,
"reportMissingTypeStubs": true,

"pythonVersion": "3.8",
"pythonVersion": "3.10",
"pythonPlatform": "Windows",

"executionEnvironments": [
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/analyze-modified-files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
- uses: actions/setup-python@v5
if: env.diff != ''
with:
python-version: 3.8
python-version: '3.10'

- name: "Install dependencies"
if: env.diff != ''
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ env:
jobs:
# build-release-macos: # LF volunteer

build-win-py38: # RCs will still be built and signed by hand
build-win-py310: # RCs will still be built and signed by hand
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install python
uses: actions/setup-python@v5
with:
python-version: '3.8'
python-version: '3.10'
- name: Download run-time dependencies
run: |
Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/${Env:ENEMIZER_VERSION}/win-x64.zip -OutFile enemizer.zip
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,11 @@ jobs:
matrix:
os: [ubuntu-latest]
python:
- {version: '3.8'}
- {version: '3.9'}
- {version: '3.10'}
- {version: '3.11'}
- {version: '3.12'}
include:
- python: {version: '3.8'} # win7 compat
- python: {version: '3.10'} # old compat
os: windows-latest
- python: {version: '3.12'} # current
os: windows-latest
Expand Down
12 changes: 5 additions & 7 deletions BaseClasses.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
from __future__ import annotations

import collections
import itertools
import functools
import logging
import random
import secrets
import typing # this can go away when Python 3.8 support is dropped
from argparse import Namespace
from collections import Counter, deque
from collections.abc import Collection, MutableSequence
from enum import IntEnum, IntFlag
from typing import (AbstractSet, Any, Callable, ClassVar, Dict, Iterable, Iterator, List, Mapping, NamedTuple,
Optional, Protocol, Set, Tuple, Union, Type)
Optional, Protocol, Set, Tuple, Union, TYPE_CHECKING)

from typing_extensions import NotRequired, TypedDict

import NetUtils
import Options
import Utils

if typing.TYPE_CHECKING:
if TYPE_CHECKING:
from worlds import AutoWorld


Expand Down Expand Up @@ -231,7 +229,7 @@ def set_options(self, args: Namespace) -> None:
for player in self.player_ids:
world_type = AutoWorld.AutoWorldRegister.world_types[self.game[player]]
self.worlds[player] = world_type(self, player)
options_dataclass: typing.Type[Options.PerGameCommonOptions] = world_type.options_dataclass
options_dataclass: type[Options.PerGameCommonOptions] = world_type.options_dataclass
self.worlds[player].options = options_dataclass(**{option_key: getattr(args, option_key)[player]
for option_key in options_dataclass.type_hints})

Expand Down Expand Up @@ -975,7 +973,7 @@ class Region:
entrances: List[Entrance]
exits: List[Entrance]
locations: List[Location]
entrance_type: ClassVar[Type[Entrance]] = Entrance
entrance_type: ClassVar[type[Entrance]] = Entrance

class Register(MutableSequence):
region_manager: MultiWorld.RegionManager
Expand Down Expand Up @@ -1075,7 +1073,7 @@ def get_connecting_entrance(self, is_main_entrance: Callable[[Entrance], bool])
return entrance.parent_region.get_connecting_entrance(is_main_entrance)

def add_locations(self, locations: Dict[str, Optional[int]],
location_type: Optional[Type[Location]] = None) -> None:
location_type: Optional[type[Location]] = None) -> None:
"""
Adds locations to the Region object, where location_type is your Location class and locations is a dict of
location names to address.
Expand Down
4 changes: 2 additions & 2 deletions ModuleUpdate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import warnings


if sys.version_info < (3, 8, 6):
raise RuntimeError("Incompatible Python Version. 3.8.7+ is supported.")
if sys.version_info < (3, 10, 15):
raise RuntimeError(f"Incompatible Python Version found: {sys.version_info}. 3.10.15+ is supported.")

# don't run update if environment is frozen/compiled or if not the parent process (skip in subprocess)
_skip_update = bool(getattr(sys, "frozen", False) or multiprocessing.parent_process())
Expand Down
5 changes: 2 additions & 3 deletions Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@

from argparse import Namespace
from settings import Settings, get_settings
from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union
from typing_extensions import TypeGuard
from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union, TypeGuard
from yaml import load, load_all, dump

try:
Expand All @@ -46,7 +45,7 @@ def as_simple_string(self) -> str:
return ".".join(str(item) for item in self)


__version__ = "0.5.1"
__version__ = "0.6.0"
version_tuple = tuplize_version(__version__)

is_linux = sys.platform.startswith("linux")
Expand Down
2 changes: 1 addition & 1 deletion WebHost.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
if typing.TYPE_CHECKING:
from flask import Flask

Utils.local_path.cached_path = os.path.dirname(__file__) or "." # py3.8 is not abs. remove "." when dropping 3.8
Utils.local_path.cached_path = os.path.dirname(__file__)
settings.no_gui = True
configpath = os.path.abspath("config.yaml")
if not os.path.exists(configpath): # fall back to config.yaml in home
Expand Down
4 changes: 1 addition & 3 deletions WebHostLib/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,5 @@ waitress>=3.0.0
Flask-Caching>=2.3.0
Flask-Compress>=1.15
Flask-Limiter>=3.8.0
bokeh>=3.1.1; python_version <= '3.8'
bokeh>=3.4.3; python_version == '3.9'
bokeh>=3.5.2; python_version >= '3.10'
bokeh>=3.5.2
markupsafe>=2.1.5
2 changes: 1 addition & 1 deletion docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ game contributions:
* **Do not introduce unit test failures/regressions.**
Archipelago supports multiple versions of Python. You may need to download older Python versions to fully test
your changes. Currently, the oldest supported version
is [Python 3.8](https://www.python.org/downloads/release/python-380/).
is [Python 3.10](https://www.python.org/downloads/release/python-31015/).
It is recommended that automated github actions are turned on in your fork to have github run unit tests after
pushing.
You can turn them on here:
Expand Down
2 changes: 1 addition & 1 deletion docs/running from source.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use that version. These steps are for developers or platforms without compiled r
## General

What you'll need:
* [Python 3.8.7 or newer](https://www.python.org/downloads/), not the Windows Store version
* [Python 3.10.15 or newer](https://www.python.org/downloads/), not the Windows Store version
* Python 3.12.x is currently the newest supported version
* pip: included in downloads from python.org, separate in many Linux distributions
* Matching C compiler
Expand Down
5 changes: 1 addition & 4 deletions kvui.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@

# kivy 2.2.0 introduced DPI awareness on Windows, but it makes the UI enter an infinitely recursive re-layout
# by setting the application to not DPI Aware, Windows handles scaling the entire window on its own, ignoring kivy's
try:
ctypes.windll.shcore.SetProcessDpiAwareness(0)
except FileNotFoundError: # shcore may not be found on <= Windows 7
pass # TODO: remove silent except when Python 3.8 is phased out.
ctypes.windll.shcore.SetProcessDpiAwareness(0)

os.environ["KIVY_NO_CONSOLELOG"] = "1"
os.environ["KIVY_NO_FILELOG"] = "1"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ def find_lib(lib, arch, libc):
"excludes": ["numpy", "Cython", "PySide2", "PIL",
"pandas"],
"zip_include_packages": ["*"],
"zip_exclude_packages": ["worlds", "sc2", "orjson"], # TODO: remove orjson here once we drop py3.8 support
"zip_exclude_packages": ["worlds", "sc2"],
"include_files": [], # broken in cx 6.14.0, we use more special sauce now
"include_msvcr": False,
"replace_paths": ["*."],
Expand Down
4 changes: 1 addition & 3 deletions worlds/AutoSNIClient.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@

from __future__ import annotations
import abc
from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union

from typing_extensions import TypeGuard
from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union, TypeGuard

from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components

Expand Down
19 changes: 6 additions & 13 deletions worlds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,12 @@ def load(self) -> bool:
start = time.perf_counter()
if self.is_zip:
importer = zipimport.zipimporter(self.resolved_path)
if hasattr(importer, "find_spec"): # new in Python 3.10
spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
assert spec, f"{self.path} is not a loadable module"
mod = importlib.util.module_from_spec(spec)
else: # TODO: remove with 3.8 support
mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0])

if mod.__package__ is not None:
mod.__package__ = f"worlds.{mod.__package__}"
else:
# load_module does not populate package, we'll have to assume mod.__name__ is correct here
# probably safe to remove with 3.8 support
mod.__package__ = f"worlds.{mod.__name__}"
spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
assert spec, f"{self.path} is not a loadable module"
mod = importlib.util.module_from_spec(spec)

mod.__package__ = f"worlds.{mod.__package__}"

mod.__name__ = f"worlds.{mod.__name__}"
sys.modules[mod.__name__] = mod
with warnings.catch_warnings():
Expand Down
6 changes: 1 addition & 5 deletions worlds/hk/Extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@

import jinja2

try:
from ast import unparse
except ImportError:
# Py 3.8 and earlier compatibility module
from astunparse import unparse
from ast import unparse

from Utils import get_text_between

Expand Down
1 change: 0 additions & 1 deletion worlds/hk/requirements.txt

This file was deleted.

30 changes: 15 additions & 15 deletions worlds/messenger/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Any, ClassVar, Dict, List, Optional, Set, TextIO
from typing import Any, ClassVar, TextIO

from BaseClasses import CollectionState, Entrance, Item, ItemClassification, MultiWorld, Tutorial
from Options import Accessibility
Expand Down Expand Up @@ -120,16 +120,16 @@ class MessengerWorld(World):
required_seals: int = 0
created_seals: int = 0
total_shards: int = 0
shop_prices: Dict[str, int]
figurine_prices: Dict[str, int]
_filler_items: List[str]
starting_portals: List[str]
plando_portals: List[str]
spoiler_portal_mapping: Dict[str, str]
portal_mapping: List[int]
transitions: List[Entrance]
shop_prices: dict[str, int]
figurine_prices: dict[str, int]
_filler_items: list[str]
starting_portals: list[str]
plando_portals: list[str]
spoiler_portal_mapping: dict[str, str]
portal_mapping: list[int]
transitions: list[Entrance]
reachable_locs: int = 0
filler: Dict[str, int]
filler: dict[str, int]

def generate_early(self) -> None:
if self.options.goal == Goal.option_power_seal_hunt:
Expand Down Expand Up @@ -178,7 +178,7 @@ def create_regions(self) -> None:
for reg_name in sub_region]

for region in complex_regions:
region_name = region.name.replace(f"{region.parent} - ", "")
region_name = region.name.removeprefix(f"{region.parent} - ")
connection_data = CONNECTIONS[region.parent][region_name]
for exit_region in connection_data:
region.connect(self.multiworld.get_region(exit_region, self.player))
Expand All @@ -191,7 +191,7 @@ def create_items(self) -> None:
# create items that are always in the item pool
main_movement_items = ["Rope Dart", "Wingsuit"]
precollected_names = [item.name for item in self.multiworld.precollected_items[self.player]]
itempool: List[MessengerItem] = [
itempool: list[MessengerItem] = [
self.create_item(item)
for item in self.item_name_to_id
if item not in {
Expand Down Expand Up @@ -290,7 +290,7 @@ def write_spoiler_header(self, spoiler_handle: TextIO) -> None:
for portal, output in portal_info:
spoiler.set_entrance(f"{portal} Portal", output, "I can write anything I want here lmao", self.player)

def fill_slot_data(self) -> Dict[str, Any]:
def fill_slot_data(self) -> dict[str, Any]:
slot_data = {
"shop": {SHOP_ITEMS[item].internal_name: price for item, price in self.shop_prices.items()},
"figures": {FIGURINES[item].internal_name: price for item, price in self.figurine_prices.items()},
Expand All @@ -316,7 +316,7 @@ def get_filler_item_name(self) -> str:
return self._filler_items.pop(0)

def create_item(self, name: str) -> MessengerItem:
item_id: Optional[int] = self.item_name_to_id.get(name, None)
item_id: int | None = self.item_name_to_id.get(name, None)
return MessengerItem(
name,
ItemClassification.progression if item_id is None else self.get_item_classification(name),
Expand Down Expand Up @@ -351,7 +351,7 @@ def get_item_classification(self, name: str) -> ItemClassification:
return ItemClassification.filler

@classmethod
def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set[int]) -> World:
def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: set[int]) -> World:
group = super().create_group(multiworld, new_player_id, players)
assert isinstance(group, MessengerWorld)

Expand Down
5 changes: 2 additions & 3 deletions worlds/messenger/client_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import subprocess
import urllib.request
from shutil import which
from typing import Any, Optional
from typing import Any
from zipfile import ZipFile
from Utils import open_file

Expand All @@ -17,7 +17,7 @@
MOD_URL = "https://api.github.com/repos/alwaysintreble/TheMessengerRandomizerModAP/releases/latest"


def ask_yes_no_cancel(title: str, text: str) -> Optional[bool]:
def ask_yes_no_cancel(title: str, text: str) -> bool | None:
"""
Wrapper for tkinter.messagebox.askyesnocancel, that creates a popup dialog box with yes, no, and cancel buttons.

Expand All @@ -33,7 +33,6 @@ def ask_yes_no_cancel(title: str, text: str) -> Optional[bool]:
return ret



def launch_game(*args) -> None:
"""Check the game installation, then launch it"""
def courier_installed() -> bool:
Expand Down
8 changes: 3 additions & 5 deletions worlds/messenger/connections.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from typing import Dict, List

CONNECTIONS: Dict[str, Dict[str, List[str]]] = {
CONNECTIONS: dict[str, dict[str, list[str]]] = {
"Ninja Village": {
"Right": [
"Autumn Hills - Left",
Expand Down Expand Up @@ -640,7 +638,7 @@
},
}

RANDOMIZED_CONNECTIONS: Dict[str, str] = {
RANDOMIZED_CONNECTIONS: dict[str, str] = {
"Ninja Village - Right": "Autumn Hills - Left",
"Autumn Hills - Left": "Ninja Village - Right",
"Autumn Hills - Right": "Forlorn Temple - Left",
Expand Down Expand Up @@ -680,7 +678,7 @@
"Sunken Shrine - Left": "Howling Grotto - Bottom",
}

TRANSITIONS: List[str] = [
TRANSITIONS: list[str] = [
"Ninja Village - Right",
"Autumn Hills - Left",
"Autumn Hills - Right",
Expand Down
Loading
Loading