Skip to content

Backport PR #4385 on branch 1.12.x (fix: multi-column groupby crashing on non-string observations) - #4517

Merged
flying-sheep merged 1 commit into
scverse:1.12.xfrom
meeseeksmachine:auto-backport-of-pr-4385-on-1.12.x
Nov 13, 2026
Merged

flying-sheep merged 1 commit into
scverse:1.12.xfrom
meeseeksmachine:auto-backport-of-pr-4385-on-1.12.x

Conversation

@meeseeksmachine

Copy link
Copy Markdown
Contributor

Backport PR #4385: fix: multi-column groupby crashing on non-string observations

@lumberbot-app lumberbot-app Bot added this to the 1.12.7 milestone Nov 12, 2026
@read-the-docs-business

Copy link
Copy Markdown

@codecov

codecov Bot commented Nov 12, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
3024 1 3023 175
View the top 1 failed test(s) by shortest run time
tests/test_datasets.py::test_ebi_expression_atlas
Stack Traces | 389s run time
self = <http.client.HTTPResponse object at 0x795ff3c35720>, amt = 8192

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_read_chunked#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, amt=#x1B[94mNone#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.chunked != _UNKNOWN#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m amt #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m #x1B[95mand#x1B[39;49;00m amt < #x1B[94m0#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            amt = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        value = []#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           #x1B[94mwhile#x1B[39;49;00m (chunk_left := #x1B[96mself#x1B[39;49;00m._get_chunk_left()) #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                                 ^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31m../................./_temp/uv-python-dir/cpython-3.14.10-linux-x86_64-gnu/lib/python3.14/http/client.py#x1B[0m:621: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31m../................./_temp/uv-python-dir/cpython-3.14.10-linux-x86_64-gnu/lib/python3.14/http/client.py#x1B[0m:601: in _get_chunk_left
    #x1B[0m#x1B[96mself#x1B[39;49;00m._safe_read(#x1B[94m2#x1B[39;49;00m)  #x1B[90m# toss the CRLF at the end of the chunk#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    ^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <http.client.HTTPResponse object at 0x795ff3c35720>, amt = 2

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_safe_read#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, amt):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Read the number of bytes requested.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    This function should be used when <amt> bytes "should" be present for#x1B[39;49;00m
    #x1B[33m    reading. If the bytes are truly not available (due to EOF), then the#x1B[39;49;00m
    #x1B[33m    IncompleteRead exception can be used to detect the problem.#x1B[39;49;00m
    #x1B[33m    """#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        cursize = #x1B[96mmin#x1B[39;49;00m(amt, _MIN_READ_BUF_SIZE)#x1B[90m#x1B[39;49;00m
        data = #x1B[96mself#x1B[39;49;00m.fp.read(cursize)#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mlen#x1B[39;49;00m(data) >= amt:#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m data#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mlen#x1B[39;49;00m(data) < cursize:#x1B[90m#x1B[39;49;00m
>           #x1B[94mraise#x1B[39;49;00m IncompleteRead(data, amt - #x1B[96mlen#x1B[39;49;00m(data))#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           http.client.IncompleteRead: IncompleteRead(0 bytes read, 2 more expected)#x1B[0m

#x1B[1m#x1B[31m../................./_temp/uv-python-dir/cpython-3.14.10-linux-x86_64-gnu/lib/python3.14/http/client.py#x1B[0m:671: IncompleteRead

#x1B[33mThe above exception was the direct cause of the following exception:#x1B[0m

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x795fad63b1e0>

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.internet#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_ebi_expression_atlas#x1B[39;49;00m(monkeypatch: pytest.MonkeyPatch):#x1B[90m#x1B[39;49;00m
        #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96mscanpy#x1B[39;49;00m#x1B[04m#x1B[96m.#x1B[39;49;00m#x1B[04m#x1B[96mdatasets#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[94mimport#x1B[39;49;00m _ebi_expression_atlas #x1B[94mas#x1B[39;49;00m ea_mod#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# make sure we use chunks when testing.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# This dataset has <8M entries, so 4M entries/chunk = 2 chunks#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(ea_mod, #x1B[33m"#x1B[39;49;00m#x1B[33mCHUNK_SIZE#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        monkeypatch.setattr(ea_mod, #x1B[33m"#x1B[39;49;00m#x1B[33mCHUNK_SIZE#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[96mint#x1B[39;49;00m(#x1B[94m4e6#x1B[39;49;00m))#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       adata = sc.datasets.ebi_expression_atlas(#x1B[33m"#x1B[39;49;00m#x1B[33mE-MTAB-4888#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtests/test_datasets.py#x1B[0m:85: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[.../scanpy/datasets/_ebi_expression_atlas.py#x1B[0m:159: in ebi_expression_atlas
    #x1B[0mdownload_experiment(accession)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../scanpy/datasets/_utils.py#x1B[0m:16: in wrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m f(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../scanpy/datasets/_ebi_expression_atlas.py#x1B[0m:58: in download_experiment
    #x1B[0m_download(mtx_url, experiment_dir / #x1B[33m"#x1B[39;49;00m#x1B[33mexpression_archive.zip#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31msrc/scanpy/readwrite.py#x1B[0m:1102: in _download
    #x1B[0mblock = resp.read(blocksize)#x1B[90m#x1B[39;49;00m
            ^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m../................./_temp/uv-python-dir/cpython-3.14.10-linux-x86_64-gnu/lib/python3.14/http/client.py#x1B[0m:488: in read
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._read_chunked(amt)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <http.client.HTTPResponse object at 0x795ff3c35720>, amt = 8192

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_read_chunked#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, amt=#x1B[94mNone#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.chunked != _UNKNOWN#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m amt #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m #x1B[95mand#x1B[39;49;00m amt < #x1B[94m0#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            amt = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        value = []#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwhile#x1B[39;49;00m (chunk_left := #x1B[96mself#x1B[39;49;00m._get_chunk_left()) #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m amt #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m #x1B[95mand#x1B[39;49;00m amt <= chunk_left:#x1B[90m#x1B[39;49;00m
                    value.append(#x1B[96mself#x1B[39;49;00m._safe_read(amt))#x1B[90m#x1B[39;49;00m
                    #x1B[96mself#x1B[39;49;00m.chunk_left = chunk_left - amt#x1B[90m#x1B[39;49;00m
                    #x1B[94mbreak#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
                value.append(#x1B[96mself#x1B[39;49;00m._safe_read(chunk_left))#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m amt #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    amt -= chunk_left#x1B[90m#x1B[39;49;00m
                #x1B[96mself#x1B[39;49;00m.chunk_left = #x1B[94m0#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m #x1B[33mb#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m.join(value)#x1B[90m#x1B[39;49;00m
        #x1B[94mexcept#x1B[39;49;00m IncompleteRead #x1B[94mas#x1B[39;49;00m exc:#x1B[90m#x1B[39;49;00m
>           #x1B[94mraise#x1B[39;49;00m IncompleteRead(#x1B[33mb#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m.join(value)) #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96mexc#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           http.client.IncompleteRead: IncompleteRead(0 bytes read)#x1B[0m

#x1B[1m#x1B[31m../................./_temp/uv-python-dir/cpython-3.14.10-linux-x86_64-gnu/lib/python3.14/http/client.py#x1B[0m:633: IncompleteRead

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@flying-sheep
flying-sheep merged commit 4064e4f into scverse:1.12.x Nov 13, 2026
4 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants