From 6256fcf69fee59fba51de14da88db2756a95b898 Mon Sep 17 00:00:00 2001 From: Ben Jeffery Date: Mon, 9 Oct 2023 15:18:51 +0100 Subject: [PATCH] Remove 3.7 and update pre-commit to 3.8 --- .github/workflows/docker/shared.env | 1 - .github/workflows/wheels.yml | 13 ++++++------- .pre-commit-config.yaml | 16 ++++++++-------- docs/development.md | 8 ++++---- docs/installation.md | 2 +- python/CHANGELOG.rst | 4 ++++ python/setup.cfg | 3 +-- python/tests/simplify.py | 3 --- python/tests/test_file_format.py | 2 +- python/tests/test_highlevel.py | 6 +++--- python/tests/test_ibd.py | 3 --- python/tests/test_lowlevel.py | 2 +- python/tests/test_table_transforms.py | 3 --- python/tests/test_tables.py | 11 ++++------- python/tests/test_text_formats.py | 4 ++-- python/tskit/combinatorics.py | 2 +- python/tskit/drawing.py | 7 ++++++- python/tskit/metadata.py | 8 ++++---- python/tskit/stats.py | 1 - python/tskit/tables.py | 2 ++ python/tskit/trees.py | 7 +++++++ python/tskit/util.py | 4 ++-- 22 files changed, 57 insertions(+), 55 deletions(-) diff --git a/.github/workflows/docker/shared.env b/.github/workflows/docker/shared.env index 26a8ea4318..3f299243a5 100644 --- a/.github/workflows/docker/shared.env +++ b/.github/workflows/docker/shared.env @@ -3,5 +3,4 @@ PYTHON_VERSIONS=( cp310-cp310 cp39-cp39 cp38-cp38 - cp37-cp37m ) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index caa1c0837c..8fbdbea831 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -15,7 +15,7 @@ jobs: runs-on: macos-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11] + python: [3.8, 3.9, "3.10", 3.11] steps: - name: Checkout uses: actions/checkout@v2 @@ -55,7 +55,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11] + python: [3.8, 3.9, "3.10", 3.11] wordsize: [64] steps: - name: Checkout @@ -140,7 +140,7 @@ jobs: runs-on: macos-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11] + python: [3.8, 3.9, "3.10", 3.11] steps: - name: Download wheels uses: actions/download-artifact@v2 @@ -162,7 +162,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11] + python: [3.8, 3.9, "3.10", 3.11] wordsize: [64] steps: - name: Download wheels @@ -186,10 +186,9 @@ jobs: needs: ['manylinux'] strategy: matrix: - python: [3.7, 3.8, 3.9, "3.10", 3.11] + python: [3.8, 3.9, "3.10", 3.11] include: - - python: 3.7 - wheel: cp37 + - python: 3.8 wheel: cp38 - python: 3.9 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7bfa1c89ae..f91e9c2e5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.5.0 hooks: - id: check-merge-conflict - id: debug-statements @@ -18,29 +18,29 @@ repos: exclude: dev-tools|examples verbose: true - repo: https://github.com/asottile/reorder_python_imports - rev: v3.9.0 + rev: v3.12.0 hooks: - id: reorder-python-imports args: [--application-directories=python, --unclassifiable-application-module=_tskit] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.15.0 hooks: - id: pyupgrade - args: [--py3-plus, --py37-plus] + args: [--py3-plus, --py38-plus] - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 23.9.1 hooks: - id: black language_version: python3 - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + rev: 6.1.0 hooks: - id: flake8 args: [--config=python/.flake8] - additional_dependencies: ["flake8-bugbear==22.10.27", "flake8-builtins==2.0.1"] + additional_dependencies: ["flake8-bugbear==23.9.16", "flake8-builtins==2.1.0"] - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 + rev: 1.16.0 hooks: - id: blacken-docs args: [--skip-errors] diff --git a/docs/development.md b/docs/development.md index 39fc890878..4558b21662 100644 --- a/docs/development.md +++ b/docs/development.md @@ -71,7 +71,7 @@ an overview of how to contribute a new feature to `tskit`. ### Requirements To develop the Python code you will need a working C compiler and a -development installation of Python (>= 3.7). On Debian/Ubuntu we can install these +development installation of Python (>= 3.8). On Debian/Ubuntu we can install these with: ```bash @@ -429,7 +429,7 @@ $ make If this has completed successfully you should see a file `_tskit.cpython-XXXXXX.so` in the current directory (the suffix depends on your platform and Python version; -with Python 3.7 on Linux it's `_tskit.cpython-37m-x86_64-linux-gnu.so`). +with Python 3.11 on Linux it's `_tskit.cpython-311-x86_64-linux-gnu.so`). To make sure that your development environment is working, run some {ref}`tests `. @@ -1050,10 +1050,10 @@ For instance: ```bash > git commit -a -m 'fixed all the things' -/usr/bin/env: ‘python3.7’: No such file or directory +/usr/bin/env: ‘python3.8’: No such file or directory ``` -What the heck? Why is this even looking for python3.7? +What the heck? Why is this even looking for python3.8? This is because of the "pre-commit hook", mentioned above. As above, you can proceed by just appending `--no-verify`: diff --git a/docs/installation.md b/docs/installation.md index 413b7bd751..6da52e12a8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -32,7 +32,7 @@ so it may already be installed if you use such software. ## Requirements -Tskit requires Python 3.7+. There are no external C library dependencies. Python +Tskit requires Python 3.8+. There are no external C library dependencies. Python dependencies are installed automatically by `pip` or `conda`. (sec_installation_conda)= diff --git a/python/CHANGELOG.rst b/python/CHANGELOG.rst index 35af671c97..baad328c4b 100644 --- a/python/CHANGELOG.rst +++ b/python/CHANGELOG.rst @@ -2,6 +2,10 @@ [0.5.6] - 2023-XX-XX -------------------- +**Breaking Changes** + +- tskit now requires Python 3.8, as Python 3.7 became end-of-life on 2023-06-27 + **Features** - Tree.trmca now accepts >2 nodes and returns nicer errors diff --git a/python/setup.cfg b/python/setup.cfg index 13a1506f25..9d15e924bb 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -16,7 +16,6 @@ classifiers = Programming Language :: C Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -46,7 +45,7 @@ platforms = [options] packages = tskit -python_requires = >=3.7 +python_requires = >=3.8 include_package_data = True install_requires = jsonschema>=3.0.0 diff --git a/python/tests/simplify.py b/python/tests/simplify.py index 6505aec05d..02e0482cca 100644 --- a/python/tests/simplify.py +++ b/python/tests/simplify.py @@ -725,7 +725,6 @@ def flush_edges(self): return num_edges def check_state(self): - num_nodes = len(self.A_head) for j in range(num_nodes): head = self.A_head[j] @@ -771,7 +770,6 @@ def print_state(self): ts = tskit.load(sys.argv[2]) if class_to_implement == "Simplifier": - samples = list(map(int, sys.argv[3:])) print("When keep_unary = True:") @@ -805,7 +803,6 @@ def print_state(self): print(tables.mutations) elif class_to_implement == "AncestorMap": - samples = sys.argv[3] samples = samples.split(",") samples = list(map(int, samples)) diff --git a/python/tests/test_file_format.py b/python/tests/test_file_format.py index 26abc0f2d8..2de38c487e 100644 --- a/python/tests/test_file_format.py +++ b/python/tests/test_file_format.py @@ -487,7 +487,7 @@ def test_unsupported_version(self): # Cannot read current files. ts.dump(self.temp_file) # Catch Exception here because h5py throws different exceptions on py2 and py3 - with pytest.raises(Exception): + with pytest.raises(Exception): # noqa B017 tskit.load_legacy(self.temp_file) def test_no_version_number(self): diff --git a/python/tests/test_highlevel.py b/python/tests/test_highlevel.py index a03608756c..089ef23148 100644 --- a/python/tests/test_highlevel.py +++ b/python/tests/test_highlevel.py @@ -207,7 +207,7 @@ def insert_gap(ts, position, length): return tables.tree_sequence() -@functools.lru_cache() +@functools.lru_cache def get_gap_examples(): """ Returns example tree sequences that contain gaps within the list of @@ -239,7 +239,7 @@ def get_gap_examples(): return ret -@functools.lru_cache() +@functools.lru_cache def get_internal_samples_examples(): """ Returns example tree sequences with internal samples. @@ -270,7 +270,7 @@ def get_internal_samples_examples(): return ret -@functools.lru_cache() +@functools.lru_cache def get_decapitated_examples(): """ Returns example tree sequences in which the oldest edges have been removed. diff --git a/python/tests/test_ibd.py b/python/tests/test_ibd.py index d995e32277..a5bf2c7c5a 100644 --- a/python/tests/test_ibd.py +++ b/python/tests/test_ibd.py @@ -285,7 +285,6 @@ def test_empty_in_between(self, ts): class TestIbdTwoSamplesTwoTrees: - # 2 # | 3 # 1 2 | / \ @@ -339,7 +338,6 @@ def test_length(self): class TestIbdUnrelatedSamples: - # # 2 3 # | | @@ -347,7 +345,6 @@ class TestIbdUnrelatedSamples: @tests.cached_example def ts(self): - nodes = io.StringIO( """\ id is_sample time diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 31ad272a05..8fc1e427a2 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -4044,7 +4044,7 @@ def test_uninitialised(): ] for cls_name, cls in inspect.getmembers(_tskit): if ( - type(cls) == type + isinstance(cls, type) and not issubclass(cls, Exception) and not issubclass(cls, tuple) ): diff --git a/python/tests/test_table_transforms.py b/python/tests/test_table_transforms.py index 8c7250c453..6ff6962aad 100644 --- a/python/tests/test_table_transforms.py +++ b/python/tests/test_table_transforms.py @@ -96,7 +96,6 @@ def test_mutation_parents(self, ts): class TestDeleteOlderSimpleTree: - # 2.00┊ 4 ┊ # ┊ ┏━┻┓ ┊ # 1.00┊ ┃ 3 ┊ @@ -357,7 +356,6 @@ def split_edges_definition(ts, time, *, flags=0, population=None, metadata=None) class TestSplitEdgesSimpleTree: - # 2.00┊ 4 ┊ # ┊ ┏━┻┓ ┊ # 1.00┊ ┃ 3 ┊ @@ -757,7 +755,6 @@ def test_no_population(self, ts): class TestDecapitateSimpleTree: - # 2.00┊ 4 ┊ # ┊ ┏━┻┓ ┊ # 1.00┊ ┃ 3 ┊ diff --git a/python/tests/test_tables.py b/python/tests/test_tables.py index 30ef6634fe..5e045ace88 100644 --- a/python/tests/test_tables.py +++ b/python/tests/test_tables.py @@ -1772,7 +1772,6 @@ def test_keep_rows_data(self): class TestNodeTable(*common_tests): - columns = [ UInt32Column("flags"), DoubleColumn("time"), @@ -1853,7 +1852,6 @@ def test_add_row_bad_data(self): class TestEdgeTable(*common_tests): - columns = [ DoubleColumn("left"), DoubleColumn("right"), @@ -3856,13 +3854,13 @@ def check_concordance(d1, d2): assert set(d1.keys()) == set(d2.keys()) for k1, v1 in d1.items(): v2 = d2[k1] - assert type(v1) == type(v2) - if type(v1) == dict: + assert type(v1) is type(v2) + if type(v1) is dict: assert set(v1.keys()) == set(v2.keys()) for sk1, sv1 in v1.items(): sv2 = v2[sk1] - assert type(sv1) == type(sv2) - if type(sv1) == np.ndarray: + assert type(sv1) is type(sv2) + if isinstance(sv1, np.ndarray): assert np.array_equal(sv1, sv2) or ( np.all(tskit.is_unknown_time(sv1)) and np.all(tskit.is_unknown_time(sv2)) @@ -4172,7 +4170,6 @@ def test_kwargs_only(self): class TestTableCollectionMetadata: - metadata_schema = metadata.MetadataSchema( { "codec": "json", diff --git a/python/tests/test_text_formats.py b/python/tests/test_text_formats.py index 75f377bc12..c00b264e6e 100644 --- a/python/tests/test_text_formats.py +++ b/python/tests/test_text_formats.py @@ -92,7 +92,7 @@ def test_insufficient_cols(self, n_cols): entry = FamEntry(iid="1") for field in fields[n_cols:]: entry.__setattr__(field, None) - with pytest.raises( + with pytest.raises( # noqa B017 Exception ): # Have to be non-specific here as numpy 1.23 changed the exception type self.get_parsed_fam(entries=[entry]) @@ -137,7 +137,7 @@ def test_bad_sex_value(self, sex): def test_empty_sex_value(self): entries = [FamEntry(iid="1", sex="")] - with pytest.raises( + with pytest.raises( # noqa B017 Exception ): # Have to be non-specific here as numpy 1.23 changed the exception type self.get_parsed_fam(entries=entries) diff --git a/python/tskit/combinatorics.py b/python/tskit/combinatorics.py index 4c25cef164..a4d2c88968 100644 --- a/python/tskit/combinatorics.py +++ b/python/tskit/combinatorics.py @@ -1126,7 +1126,7 @@ def is_symmetrical(self): # so we should compute a vector of those results up front instead of using # repeated calls to this function. # Put an lru_cache on for now as a quick replacement (cuts test time down by 80%) -@functools.lru_cache() +@functools.lru_cache def num_shapes(n): """ The cardinality of the set of unlabelled trees with n leaves, diff --git a/python/tskit/drawing.py b/python/tskit/drawing.py index 51e0260cd6..e7ea7e4fff 100644 --- a/python/tskit/drawing.py +++ b/python/tskit/drawing.py @@ -403,6 +403,7 @@ def draw_tree( warnings.warn( "tree_height_scale is deprecated; use time_scale instead", FutureWarning, + stacklevel=4, ) if max_time is None and max_tree_height is not None: max_time = max_tree_height @@ -410,6 +411,7 @@ def draw_tree( warnings.warn( "max_tree_height is deprecated; use max_time instead", FutureWarning, + stacklevel=4, ) # See tree.draw() for documentation on these arguments. @@ -1021,6 +1023,7 @@ def __init__( warnings.warn( "max_tree_height is deprecated; use max_time instead", FutureWarning, + stacklevel=4, ) if time_scale is None and tree_height_scale is not None: time_scale = tree_height_scale @@ -1028,6 +1031,7 @@ def __init__( warnings.warn( "tree_height_scale is deprecated; use time_scale instead", FutureWarning, + stacklevel=4, ) x_lim = check_x_lim(x_lim, max_x=ts.sequence_length) ts, self.tree_status, offsets = clip_ts(ts, x_lim[0], x_lim[1], max_num_trees) @@ -1230,7 +1234,6 @@ def draw_x_axis( self.plotbox.pad_bottom + self.tree_plotbox.pad_bottom, ) else: - # For a treewise plot, the only time the x_transform is used is to apply # to tick positions, so simply use positions 0..num_used_breaks for the # positions, and a simple transform @@ -1304,6 +1307,7 @@ def __init__( warnings.warn( "max_tree_height is deprecated; use max_time instead", FutureWarning, + stacklevel=4, ) if time_scale is None and tree_height_scale is not None: time_scale = tree_height_scale @@ -1311,6 +1315,7 @@ def __init__( warnings.warn( "tree_height_scale is deprecated; use time_scale instead", FutureWarning, + stacklevel=4, ) if size is None: size = (200, 200) diff --git a/python/tskit/metadata.py b/python/tskit/metadata.py index 94b00a7f40..9b17c2f2cc 100644 --- a/python/tskit/metadata.py +++ b/python/tskit/metadata.py @@ -46,9 +46,9 @@ def replace_root_refs(obj): - if type(obj) == list: + if type(obj) is list: return [replace_root_refs(j) for j in obj] - elif type(obj) == dict: + elif type(obj) is dict: ret = {k: replace_root_refs(v) for k, v in obj.items()} if ret.get("$ref") == "#": ret["$ref"] = "#/definitions/root" @@ -558,9 +558,9 @@ def modify_schema(cls, schema: Mapping) -> Mapping: # we add it here, sadly we can't do this in the metaschema as "default" isn't # used by the validator. def enforce_fixed_properties(obj): - if type(obj) == list: + if type(obj) is list: return [enforce_fixed_properties(j) for j in obj] - elif type(obj) == dict: + elif type(obj) is dict: ret = {k: enforce_fixed_properties(v) for k, v in obj.items()} if "object" in ret.get("type", []): if ret.get("additional_properties"): diff --git a/python/tskit/stats.py b/python/tskit/stats.py index a972b01454..74101bf017 100644 --- a/python/tskit/stats.py +++ b/python/tskit/stats.py @@ -588,7 +588,6 @@ def __init__( blocks_per_window=None, span_normalise=True, ): - assert isinstance(ts, trees.TreeSequence) if sample_sets is None: diff --git a/python/tskit/tables.py b/python/tskit/tables.py index cb2ff7d01f..bedeaff4fa 100644 --- a/python/tskit/tables.py +++ b/python/tskit/tables.py @@ -3070,6 +3070,7 @@ def name_map(self) -> Dict: warnings.warn( "name_map is deprecated; use table_name_map instead", FutureWarning, + stacklevel=4, ) return self.table_name_map @@ -3437,6 +3438,7 @@ def simplify( warnings.warn( "filter_zero_mutation_sites is deprecated; use filter_sites instead", FutureWarning, + stacklevel=4, ) filter_sites = filter_zero_mutation_sites if samples is None: diff --git a/python/tskit/trees.py b/python/tskit/trees.py index 84113566b9..df2fa2faf1 100644 --- a/python/tskit/trees.py +++ b/python/tskit/trees.py @@ -661,6 +661,7 @@ def __init__( "The sample_counts option is not supported since 0.2.4 " "and is ignored", RuntimeWarning, + stacklevel=4, ) if sample_lists: options |= _tskit.SAMPLE_LISTS @@ -1513,6 +1514,7 @@ def num_nodes(self): "in the topology of the current tree (i.e. reachable from the roots) " "use len(tree.preorder()).", FutureWarning, + stacklevel=4, ) return self.tree_sequence.num_nodes @@ -4121,6 +4123,7 @@ def dump(self, file_or_path, zlib_compression=False): warnings.warn( "The zlib_compression option is no longer supported and is ignored", RuntimeWarning, + stacklevel=4, ) file, local_file = util.convert_file_like_to_open_file(file_or_path, "wb") try: @@ -5226,6 +5229,7 @@ def haplotypes( " be removed. Use ``isolated_as_missing=False`` instead of" "``impute_missing_data=True``.", FutureWarning, + stacklevel=4, ) # Only use impute_missing_data if isolated_as_missing has the default value if isolated_as_missing is None: @@ -5325,6 +5329,7 @@ def variants( " be removed. Use ``isolated_as_missing=False`` instead of" "``impute_missing_data=True``.", FutureWarning, + stacklevel=4, ) # Only use impute_missing_data if isolated_as_missing has the default value if isolated_as_missing is None: @@ -5410,6 +5415,7 @@ def genotype_matrix( " be removed. Use ``isolated_as_missing=False`` instead of" "``impute_missing_data=True``.", FutureWarning, + stacklevel=4, ) # Only use impute_missing_data if isolated_as_missing has the default value if isolated_as_missing is None: @@ -8205,6 +8211,7 @@ def trait_regression(self, *args, **kwargs): warnings.warn( "This is deprecated: please use trait_linear_model( ) instead.", FutureWarning, + stacklevel=4, ) return self.trait_linear_model(*args, **kwargs) diff --git a/python/tskit/util.py b/python/tskit/util.py index 28e9876b5a..b082dc2ab1 100644 --- a/python/tskit/util.py +++ b/python/tskit/util.py @@ -336,7 +336,7 @@ def obj_to_collapsed_html(d, name=None, open_depth=0): opened = "open" if open_depth > 0 else "" open_depth -= 1 name = str(name) + ":" if name is not None else "" - if type(d) == dict: + if type(d) is dict: return f"""
{name} @@ -347,7 +347,7 @@ def obj_to_collapsed_html(d, name=None, open_depth=0):
""" - elif type(d) == list: + elif type(d) is list: return f"""
{name}