diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 3131c32..52011d3 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -12,34 +12,6 @@ on: jobs: - # Test on Python 2.7 - test-linux-27: - - name: Linux (Python 2.7) - runs-on: ubuntu-20.04 - container: python:2.7 - - env: - SDL_VIDEODRIVER: dummy - SDL_AUDIODRIVER: dummy - SDL_RENDER_DRIVER: software - - steps: - - uses: actions/checkout@v2 - - - name: Install dependencies for testing - run: | - apt update && apt install -y --fix-missing libgl1-mesa-dev - python -m pip install --upgrade pip - python -m pip install pytest mock - - - name: Install and test KLibs - run: | - python -m pip install . - klibs -h - pytest -vvl -rxXP - - # Test on all supported Python 3.x versions with Linux test-linux: @@ -49,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] name-prefix: ['Linux (Python '] env: @@ -87,7 +59,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9] + python-version: ['3.10'] name-prefix: ['macOS (Python '] env: @@ -123,11 +95,11 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9] + python-version: ['3.10'] architecture: ['x64'] name-prefix: ['Windows (Python '] include: - - python-version: '3.9' + - python-version: '3.10' architecture: 'x86' name-prefix: 'Windows 32-bit (Python ' diff --git a/README.md b/README.md index 0361adb..c9f1c84 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ KLibs also aims to make the distribution, replication, and modification of parad ## Dependencies -The only dependencies needed to install KLibs on macOS, Windows, and most Linux distros are Git and a supported version of Python. KLibs requires either Python 3.7 (or newer) or Python 2.7 to run. +The only dependencies needed to install KLibs on macOS, Windows, and most Linux distros are Git and a supported version of Python. KLibs requires Python 3.7 (or newer) run. You will also need the pip Python package manager to install KLibs on your system. If running 'pip --version' on your system results in a "command not found" message, you can install it using the [official instructions](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst index 88f1026..e1c66be 100644 --- a/docs/CHANGELOG.rst +++ b/docs/CHANGELOG.rst @@ -3,6 +3,20 @@ Changelog This is a log of the latest changes and improvements to KLibs. +0.8.0a1 +------- + +(Unreleased) + +Runtime Changes: + +* KLibs now requires Python 3.7 or newer to run, dropping support for 2.7. + +Fixed Bugs: + +* KLibs no longer crashes on launch with Python 3.12. + + 0.7.7b1 ------- @@ -42,6 +56,7 @@ New Features: Runtime Changes: + * The way that trials are internally generated and randomized has been changed, breaking random seed compatibility with older releases. * Previously, it was possible (albeit unlikely) for a block with more than diff --git a/docs/source/conf.py b/docs/source/conf.py index bdac046..91cb2f9 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,7 +15,7 @@ import sys import os import shlex -import pkg_resources +from importlib.metadata import version as pkg_version import sphinx_readable_theme # If extensions (or modules to document with autodoc) are in another directory, @@ -63,7 +63,7 @@ # General information about the project. project = u'klibs' -copyright = u'2018, Austin Hurst & Jonathan Mulle' +copyright = u'2024, Austin Hurst & Jonathan Mulle' author = u'Austin Hurst & Jonathan Mulle' # The version info for the project you're documenting, acts as replacement for @@ -71,7 +71,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = pkg_resources.require("klibs")[0].version +release = pkg_version('klibs') # The short X.Y version. version = release[:3] diff --git a/klibs/KLDatabase.py b/klibs/KLDatabase.py index 8852eb1..b5c444a 100755 --- a/klibs/KLDatabase.py +++ b/klibs/KLDatabase.py @@ -291,7 +291,6 @@ class Database(object): def __init__(self, path): super(Database, self).__init__() self.db = sqlite3.connect(path, detect_types=sqlite3.PARSE_DECLTYPES) - self.db.text_factory = sqlite3.OptimizedUnicode self.cursor = self.db.cursor() self.table_schemas = self._build_table_schemas() diff --git a/klibs/KLInternal.py b/klibs/KLInternal.py index 7c6eca7..0a7c726 100644 --- a/klibs/KLInternal.py +++ b/klibs/KLInternal.py @@ -103,14 +103,10 @@ def load_source(filepath): mod_name = "mod_{0}".format(binascii.b2a_hex(os.urandom(4))) # Load Python file as a module - if sys.version_info.major == 3: - from importlib.util import spec_from_file_location, module_from_spec - spec = spec_from_file_location(mod_name, filepath) - src = module_from_spec(spec) - spec.loader.exec_module(src) - else: - import imp - src = imp.load_source(mod_name, filepath) + from importlib.util import spec_from_file_location, module_from_spec + spec = spec_from_file_location(mod_name, filepath) + src = module_from_spec(spec) + spec.loader.exec_module(src) # Filter out modules and internal Python stuff from imported attributes attributes = {} @@ -124,8 +120,6 @@ def load_source(filepath): def package_available(name): """Checks whether a given package is installed. - Written to be Python 2/3 agnostic. - Args: name (str): Name of the Python package to search for. @@ -133,10 +127,7 @@ def package_available(name): bool: True if the package is available, otherwise False. """ - if sys.version_info.major == 3: - from importlib.util import find_spec - else: - from imp import find_module as find_spec + from importlib.util import find_spec try: return find_spec(name) != None except (ValueError, ImportError): diff --git a/klibs/KLParams.py b/klibs/KLParams.py index 2974170..a2cf23d 100755 --- a/klibs/KLParams.py +++ b/klibs/KLParams.py @@ -14,7 +14,7 @@ __author__ = 'Jonathan Mulle & Austin Hurst' -from os.path import join +from os.path import join, dirname # TODO: Try making the Params "P" an object or AttributeDict? Could set attributes # dynamically but also allow for sanity checks and renaming variables w/o breaking @@ -234,7 +234,7 @@ def initialize_runtime(exp_name, randseed): """ import random import tempfile - from pkg_resources import resource_filename, resource_string + from importlib.util import find_spec global random_seed global klibs_commit @@ -252,8 +252,9 @@ def initialize_runtime(exp_name, randseed): database_local_path = join(tempfile.gettempdir(), database_local_filename) # Load extra resources from KLibs package - klibs_commit_raw = resource_string('klibs', 'resources/current_commit.txt') - klibs_commit = str(klibs_commit_raw.decode('utf-8')) - logo_file_path = resource_filename('klibs', 'resources/splash.png') - font_dirs = [exp_font_dir, resource_filename('klibs', 'resources/font')] + klibs_root = dirname(find_spec("klibs").origin) + klibs_commit_path = join(klibs_root, 'resources', 'current_commit.txt') + klibs_commit = open(klibs_commit_path, mode='r').read() + logo_file_path = join(klibs_root, 'resources', 'splash.png') + font_dirs = [exp_font_dir, join(klibs_root, 'resources', 'font')] \ No newline at end of file diff --git a/klibs/cli.py b/klibs/cli.py index 22ea949..61d5ec3 100644 --- a/klibs/cli.py +++ b/klibs/cli.py @@ -141,7 +141,7 @@ def create(name, path): from random import choice from os.path import join from tempfile import mkdtemp - from pkg_resources import resource_filename + from importlib.util import find_spec template_files = [ ("schema.sql", ["ExpAssets", "Config"]), @@ -216,7 +216,8 @@ def create(name, path): ensure_directory_structure(tmp_path, create_missing=True) cso(" ...Project template folders successfully created.") - source_path = resource_filename('klibs', 'resources/template') + klibs_root = os.path.dirname(find_spec("klibs").origin) + source_path = os.path.join(klibs_root, 'resources', 'template') for tf in template_files: # replace generic file names with project-specific names filename = tf[0] if tf[0] in [".gitignore", "experiment.py"] else "{0}_{1}".format(name, tf[0]) template_f_path = join(source_path, tf[0] if tf[0] != ".gitignore" else "gitignore.txt") diff --git a/klibs/tests/conftest.py b/klibs/tests/conftest.py index adcda06..0900b79 100644 --- a/klibs/tests/conftest.py +++ b/klibs/tests/conftest.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- +import os +import tempfile +from importlib.util import find_spec + import sdl2 import pytest -import tempfile -from pkg_resources import resource_filename from klibs import P @@ -20,6 +22,11 @@ def _init_params_pytest(): P.screen_x, P.screen_y, P.refresh_rate = (1920, 1080, 60.0) +def get_resource_path(resource): + klibs_root = os.path.dirname(find_spec("klibs").origin) + return os.path.join(klibs_root, 'resources', resource) + + @pytest.fixture(scope='module') def with_sdl(): sdl2.SDL_ClearError() @@ -33,7 +40,7 @@ def with_sdl(): def with_txtm(with_sdl): import klibs.KLEnvironment as env from klibs.KLText import TextManager - P.font_dirs = [resource_filename('klibs', 'resources/font')] + P.font_dirs = [get_resource_path('font')] P.exp_font_dir = tempfile.gettempdir() env.txtm = TextManager() yield diff --git a/klibs/tests/test_KLDatabase.py b/klibs/tests/test_KLDatabase.py index 897e536..0d6b1d9 100644 --- a/klibs/tests/test_KLDatabase.py +++ b/klibs/tests/test_KLDatabase.py @@ -1,16 +1,15 @@ import os import tempfile import pytest -from pkg_resources import resource_filename import klibs from klibs import KLDatabase as kldb from klibs.KLRuntimeInfo import runtime_info_init -from conftest import _init_params_pytest +from conftest import _init_params_pytest, get_resource_path -schema_path = resource_filename('klibs', 'resources/template/schema.sql') +schema_path = get_resource_path('template/schema.sql') @pytest.fixture def db_test_path(): diff --git a/klibs/tests/test_KLExperiment.py b/klibs/tests/test_KLExperiment.py index c29eaae..f493de5 100755 --- a/klibs/tests/test_KLExperiment.py +++ b/klibs/tests/test_KLExperiment.py @@ -1,17 +1,18 @@ -import pytest -import mock import os -from pkg_resources import resource_filename +import mock +import pytest import klibs from klibs.KLJSON_Object import AttributeDict +from conftest import get_resource_path + @pytest.fixture def experiment(): from klibs.KLExperiment import Experiment from klibs import P - template_path = resource_filename('klibs', 'resources/template') + template_path = get_resource_path('template') P.ind_vars_file_path = os.path.join(template_path, "independent_variables.py") P.ind_vars_file_local_path = os.path.join(template_path, "doesnt_exist.py") P.manual_trial_generation = True diff --git a/klibs/tests/test_KLNumpySurface.py b/klibs/tests/test_KLNumpySurface.py index 9ee3869..0fc47bb 100644 --- a/klibs/tests/test_KLNumpySurface.py +++ b/klibs/tests/test_KLNumpySurface.py @@ -3,10 +3,11 @@ import numpy as np from PIL import Image, ImageDraw from aggdraw import Draw -from pkg_resources import resource_filename from klibs.KLGraphics import KLDraw as kld from klibs.KLGraphics import NumpySurface, aggdraw_to_numpy_surface +from conftest import get_resource_path + def maketestsurface(): testarr = np.zeros((100, 100, 4), dtype=np.uint8) @@ -71,7 +72,7 @@ def test_init_aggdraw(self): assert surf.content[0][0][0] == 255 def test_init_file(self): - logo_file_path = resource_filename('klibs', 'resources/splash.png') + logo_file_path = get_resource_path('splash.png') surf = NumpySurface(logo_file_path) assert surf.height == 123 and surf.width == 746 assert surf.content[0][0][0] == 0 diff --git a/klibs/tests/test_KLParams.py b/klibs/tests/test_KLParams.py index 69ab7b8..ea704bb 100644 --- a/klibs/tests/test_KLParams.py +++ b/klibs/tests/test_KLParams.py @@ -7,8 +7,6 @@ # Helpers and fixtures -is_python2 = sys.version_info[0] == 2 - _path_vars = [ "project_name", "database_path", @@ -78,7 +76,7 @@ def test_initialize_runtime(with_clean_params): # Check that the random seed was set correctly assert P.random_seed == 530453080 - expected = [13, 95, 1, 12, 98] if is_python2 else [16, 28, 22, 2, 6] + expected = [16, 28, 22, 2, 6] assert [random.randint(0, 100) for i in range(0, 5)] == expected # Check that the resources and paths loaded correctly diff --git a/klibs/tests/test_KLStructure.py b/klibs/tests/test_KLStructure.py index 2876ee1..9b1ebb5 100644 --- a/klibs/tests/test_KLStructure.py +++ b/klibs/tests/test_KLStructure.py @@ -7,8 +7,6 @@ from klibs.KLStructure import FactorSet from klibs.KLTrialFactory import _generate_blocks -is_python3 = sys.version_info[0] == 3 - class TestFactorSet(object): @@ -132,9 +130,8 @@ def test_generate_blocks(): assert all(len(b) == 48 for b in blocks) # Test whether random seed works as expected - if is_python3: - random.seed(308053045) - block = _generate_blocks(tst._factors, 1, 20)[0] - assert block[0]['soa'] == 200 and block[0]['cue_loc'] == 'none' - assert block[1]['soa'] == 0 and block[1]['easy_trial'] == True - assert block[2]['soa'] == 800 and block[2]['cue_loc'] == 'right' + random.seed(308053045) + block = _generate_blocks(tst._factors, 1, 20)[0] + assert block[0]['soa'] == 200 and block[0]['cue_loc'] == 'none' + assert block[1]['soa'] == 0 and block[1]['easy_trial'] == True + assert block[2]['soa'] == 800 and block[2]['cue_loc'] == 'right' diff --git a/setup.py b/setup.py index 64d068d..c6f1c6d 100755 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ packages=['klibs', 'klibs/KLGraphics', 'klibs/KLEyeTracking'], include_package_data=True, entry_points = {'console_scripts': ['klibs = klibs.__main__:klibs_main']}, - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*', + python_requires='>=3.7', install_requires=[ 'numpy>=1.8.0rc1', 'pysdl2>=0.9.7',