Skip to content

perf(score_genes): avoid copy-heavy sparse nan mean path#4159

Open
SID-6921 wants to merge 5 commits into
scverse:mainfrom
SID-6921:perf/sparse-nanmean-no-copy
Open

perf(score_genes): avoid copy-heavy sparse nan mean path#4159
SID-6921 wants to merge 5 commits into
scverse:mainfrom
SID-6921:perf/sparse-nanmean-no-copy

Conversation

@SID-6921

Copy link
Copy Markdown
  • Problem: _sparse_nanmean currently makes sparse copies and eliminate_zeros calls.
  • Change: aggregate sums and NaN counts directly via compressed index pointers (csr/csc), no matrix copies.
  • Correctness: preserves np.nanmean-equivalent behavior for sparse matrices.
  • Tests: expanded test_sparse_nanmean to run on both csr and csc formats.
  • Validation command: ANNDATA_ZARR_WRITE_FORMAT=3 python -m pytest tests/test_score_genes.py -k sparse_nanmean -q
  • Closes _sparse_nanmean is inefficient #1894

Copilot AI review requested due to automatic review settings June 14, 2026 07:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR optimizes _sparse_nanmean() for compressed sparse matrices and expands unit tests to validate behavior across sparse storage formats.

Changes:

  • Reworked _sparse_nanmean() to avoid sparse matrix copies/eliminate_zeros() and compute reductions via indptr-based aggregation.
  • Added explicit runtime validation for axis values.
  • Extended tests to run _sparse_nanmean() against both CSR and CSC inputs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
tests/test_score_genes.py Parameterizes the test to exercise both CSR and CSC matrix formats.
src/scanpy/tools/_score_genes.py Replaces copy-heavy NaN-mean computation with a pointer-based aggregation approach and adds axis validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +59 to +60
with np.errstate(invalid="ignore", divide="ignore"):
return sums / counts
Comment on lines +48 to +56
segment_ids = np.repeat(np.arange(out_size), segment_lengths)
isnan = np.isnan(mat.data)

sums = np.bincount(
segment_ids[~isnan],
weights=mat.data[~isnan],
minlength=out_size,
).astype(np.float64, copy=False)
nan_counts = np.bincount(segment_ids[isnan], minlength=out_size)
Comment on lines +37 to +39
if axis not in (0, 1):
msg = "axis must be 0 or 1"
raise ValueError(msg)
@SID-6921

Copy link
Copy Markdown
Author

Maintainers: this PR is failing the metadata gate because I cannot apply labels from a fork. Could you please add the
o milestone\ label (or assign a milestone)? The code checks pass locally.

@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

❌ 5 Tests Failed:

Tests completed Failed Passed Skipped
1864 5 1859 926
View the top 3 failed test(s) by shortest run time
tests/test_logging.py::test_defaults
Stack Traces | 0.003s run time
caplog = <_pytest.logging.LogCaptureFixture object at 0x7f5f5b3ae7b0>
original_settings = mappingproxy({'preset': <Preset.ScanpyV1: 'scanpy-v1'>, 'verbosity': <Verbosity.warning: 1>, 'root_logger': <_RootLogg...x_memory': 15.0, 'n_jobs': 4, 'categories_to_ignore': frozenset({'?', 'N/A', 'dontknow', 'no_gate'}), 'logpath': None})

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_defaults#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        caplog: pytest.LogCaptureFixture, original_settings: Mapping[#x1B[96mstr#x1B[39;49;00m, #x1B[96mobject#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m s.logpath #x1B[95mis#x1B[39;49;00m original_settings[#x1B[33m"#x1B[39;49;00m#x1B[33mlogpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m s.logfile #x1B[95mis#x1B[39;49;00m original_settings[#x1B[33m"#x1B[39;49;00m#x1B[33mlogfile#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] #x1B[95mis#x1B[39;49;00m sys.stderr#x1B[90m#x1B[39;49;00m
        #x1B[90m# we override s.verbosity, so we only check the default here:#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m original_settings[#x1B[33m"#x1B[39;49;00m#x1B[33mverbosity#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] #x1B[95mis#x1B[39;49;00m Verbosity.warning#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# check logging handler file and level#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       [handler] = (h #x1B[94mfor#x1B[39;49;00m h #x1B[95min#x1B[39;49;00m s._root_logger.handlers #x1B[94mif#x1B[39;49;00m h #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m caplog.handler)#x1B[90m#x1B[39;49;00m
                                ^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtests/test_logging.py#x1B[0m:32: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Settings(preset=<Preset.ScanpyV1: 'scanpy-v1'>, verbosity=<Verbosity.hint: 3>, root_logger=<_RootLogger root (HINT)>, ...nore=frozenset({'?', 'N/A', 'dontknow', 'no_gate'}), logpath=PosixPath("<_io.FileIO name=12 mode='rb+' closefd=True>"))
item = '_root_logger'

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__getattr__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, item: #x1B[96mstr#x1B[39;49;00m) -> Any:#x1B[90m#x1B[39;49;00m
        private_attributes = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__private_attributes__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m private_attributes:#x1B[90m#x1B[39;49;00m
            attribute = private_attributes[item]#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(attribute, #x1B[33m'#x1B[39;49;00m#x1B[33m__get__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m attribute.#x1B[92m__get__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m))  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[90m# Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.__pydantic_private__[item]  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mKeyError#x1B[39;49;00m #x1B[94mas#x1B[39;49;00m exc:#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m) #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[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# `__pydantic_extra__` can fail to be set if the model is not yet fully initialized.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# See `BaseModel.__repr_args__` for more details#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__pydantic_extra__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m pydantic_extra #x1B[95mand#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m pydantic_extra:#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m pydantic_extra[item]#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.#x1B[91m__class__#x1B[39;49;00m, item):#x1B[90m#x1B[39;49;00m
                    #x1B[94mreturn#x1B[39;49;00m #x1B[96msuper#x1B[39;49;00m().#x1B[92m__getattribute__#x1B[39;49;00m(item)  #x1B[90m# Raises AttributeError if appropriate#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    #x1B[90m# this is the current error#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>                   #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE                   AttributeError: 'Settings' object has no attribute '_root_logger'. Did you mean: 'root_logger'?#x1B[0m

#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../site-packages/pydantic/main.py#x1B[0m:1042: AttributeError
tests/test_logging.py::test_formats
Stack Traces | 0.003s run time
capsys = <_pytest.capture.CaptureFixture object at 0x7f5f63531010>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_formats#x1B[39;49;00m(capsys: pytest.CaptureFixture):#x1B[90m#x1B[39;49;00m
        s.logfile = sys.stderr#x1B[90m#x1B[39;49;00m
        s.verbosity = Verbosity.debug#x1B[90m#x1B[39;49;00m
>       log.error(#x1B[33m"#x1B[39;49;00m#x1B[33m0#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtests/test_logging.py#x1B[0m:57: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/scanpy/logging.py#x1B[0m:219: in error
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m settings._root_logger.error(msg, time=time, deep=deep, extra=extra)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Settings(preset=<Preset.ScanpyV1: 'scanpy-v1'>, verbosity=<Verbosity.debug: 4>, root_logger=<_RootLogger root (DEBUG)>...on='lzf', max_memory=15.0, n_jobs=4, categories_to_ignore=frozenset({'?', 'N/A', 'dontknow', 'no_gate'}), logpath=None)
item = '_root_logger'

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__getattr__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, item: #x1B[96mstr#x1B[39;49;00m) -> Any:#x1B[90m#x1B[39;49;00m
        private_attributes = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__private_attributes__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m private_attributes:#x1B[90m#x1B[39;49;00m
            attribute = private_attributes[item]#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(attribute, #x1B[33m'#x1B[39;49;00m#x1B[33m__get__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m attribute.#x1B[92m__get__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m))  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[90m# Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.__pydantic_private__[item]  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mKeyError#x1B[39;49;00m #x1B[94mas#x1B[39;49;00m exc:#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m) #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[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# `__pydantic_extra__` can fail to be set if the model is not yet fully initialized.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# See `BaseModel.__repr_args__` for more details#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__pydantic_extra__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m pydantic_extra #x1B[95mand#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m pydantic_extra:#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m pydantic_extra[item]#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.#x1B[91m__class__#x1B[39;49;00m, item):#x1B[90m#x1B[39;49;00m
                    #x1B[94mreturn#x1B[39;49;00m #x1B[96msuper#x1B[39;49;00m().#x1B[92m__getattribute__#x1B[39;49;00m(item)  #x1B[90m# Raises AttributeError if appropriate#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    #x1B[90m# this is the current error#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>                   #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE                   AttributeError: 'Settings' object has no attribute '_root_logger'. Did you mean: 'root_logger'?#x1B[0m

#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../site-packages/pydantic/main.py#x1B[0m:1042: AttributeError
tests/test_logging.py::test_logfile
Stack Traces | 0.003s run time
tmp_path = PosixPath('.../pytest-0/popen-gw1/test_logfile0')
caplog = <_pytest.logging.LogCaptureFixture object at 0x7f5f5f22aba0>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_logfile#x1B[39;49;00m(tmp_path: Path, caplog: pytest.LogCaptureFixture):#x1B[90m#x1B[39;49;00m
        s.verbosity = Verbosity.hint#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        io = StringIO()#x1B[90m#x1B[39;49;00m
        s.logfile = io#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m s.logfile #x1B[95mis#x1B[39;49;00m io#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m s.logpath #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       log.error(#x1B[33m"#x1B[39;49;00m#x1B[33mtest!#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtests/test_logging.py#x1B[0m:90: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/scanpy/logging.py#x1B[0m:219: in error
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m settings._root_logger.error(msg, time=time, deep=deep, extra=extra)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Settings(preset=<Preset.ScanpyV1: 'scanpy-v1'>, verbosity=<Verbosity.hint: 3>, root_logger=<_RootLogger root (HINT)>, ...on='lzf', max_memory=15.0, n_jobs=4, categories_to_ignore=frozenset({'?', 'N/A', 'dontknow', 'no_gate'}), logpath=None)
item = '_root_logger'

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__getattr__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, item: #x1B[96mstr#x1B[39;49;00m) -> Any:#x1B[90m#x1B[39;49;00m
        private_attributes = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__private_attributes__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m private_attributes:#x1B[90m#x1B[39;49;00m
            attribute = private_attributes[item]#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(attribute, #x1B[33m'#x1B[39;49;00m#x1B[33m__get__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m attribute.#x1B[92m__get__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m))  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[90m# Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.__pydantic_private__[item]  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mKeyError#x1B[39;49;00m #x1B[94mas#x1B[39;49;00m exc:#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m) #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[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# `__pydantic_extra__` can fail to be set if the model is not yet fully initialized.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# See `BaseModel.__repr_args__` for more details#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__pydantic_extra__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m pydantic_extra #x1B[95mand#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m pydantic_extra:#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m pydantic_extra[item]#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.#x1B[91m__class__#x1B[39;49;00m, item):#x1B[90m#x1B[39;49;00m
                    #x1B[94mreturn#x1B[39;49;00m #x1B[96msuper#x1B[39;49;00m().#x1B[92m__getattribute__#x1B[39;49;00m(item)  #x1B[90m# Raises AttributeError if appropriate#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    #x1B[90m# this is the current error#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>                   #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE                   AttributeError: 'Settings' object has no attribute '_root_logger'. Did you mean: 'root_logger'?#x1B[0m

#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../site-packages/pydantic/main.py#x1B[0m:1042: AttributeError
tests/test_logging.py::test_records
Stack Traces | 0.003s run time
caplog = <_pytest.logging.LogCaptureFixture object at 0x7f5f5c158ad0>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_records#x1B[39;49;00m(caplog: pytest.LogCaptureFixture) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        s.verbosity = Verbosity.debug#x1B[90m#x1B[39;49;00m
>       log.error(#x1B[33m"#x1B[39;49;00m#x1B[33m0#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtests/test_logging.py#x1B[0m:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/scanpy/logging.py#x1B[0m:219: in error
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m settings._root_logger.error(msg, time=time, deep=deep, extra=extra)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Settings(preset=<Preset.ScanpyV1: 'scanpy-v1'>, verbosity=<Verbosity.debug: 4>, root_logger=<_RootLogger root (DEBUG)>...nore=frozenset({'?', 'N/A', 'dontknow', 'no_gate'}), logpath=PosixPath("<_io.FileIO name=12 mode='rb+' closefd=True>"))
item = '_root_logger'

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__getattr__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, item: #x1B[96mstr#x1B[39;49;00m) -> Any:#x1B[90m#x1B[39;49;00m
        private_attributes = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__private_attributes__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m private_attributes:#x1B[90m#x1B[39;49;00m
            attribute = private_attributes[item]#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(attribute, #x1B[33m'#x1B[39;49;00m#x1B[33m__get__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m attribute.#x1B[92m__get__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m))  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[90m# Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.__pydantic_private__[item]  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mKeyError#x1B[39;49;00m #x1B[94mas#x1B[39;49;00m exc:#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m) #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[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# `__pydantic_extra__` can fail to be set if the model is not yet fully initialized.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# See `BaseModel.__repr_args__` for more details#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__pydantic_extra__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m pydantic_extra #x1B[95mand#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m pydantic_extra:#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m pydantic_extra[item]#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.#x1B[91m__class__#x1B[39;49;00m, item):#x1B[90m#x1B[39;49;00m
                    #x1B[94mreturn#x1B[39;49;00m #x1B[96msuper#x1B[39;49;00m().#x1B[92m__getattribute__#x1B[39;49;00m(item)  #x1B[90m# Raises AttributeError if appropriate#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    #x1B[90m# this is the current error#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>                   #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE                   AttributeError: 'Settings' object has no attribute '_root_logger'. Did you mean: 'root_logger'?#x1B[0m

#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../site-packages/pydantic/main.py#x1B[0m:1042: AttributeError
tests/test_plotting.py::test_scatter_embedding_add_outline_vmin_vmax_norm
Stack Traces | 0.025s run time
image_comparer = <function image_comparer.<locals>.save_and_compare at 0x7f0baccc3e20>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_scatter_embedding_add_outline_vmin_vmax_norm#x1B[39;49;00m(image_comparer):#x1B[90m#x1B[39;49;00m
        save_and_compare_images = partial(image_comparer, ROOT, tol=#x1B[94m15#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        pbmc = pbmc68k_reduced()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       sc.pl.embedding(#x1B[90m#x1B[39;49;00m
            pbmc,#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mX_umap#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            color=[#x1B[33m"#x1B[39;49;00m#x1B[33mpercent_mito#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mn_counts#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mbulk_labels#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mpercent_mito#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
            s=#x1B[94m200#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            frameon=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            add_outline=#x1B[94mTrue#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            vmax=[#x1B[33m"#x1B[39;49;00m#x1B[33mp99.0#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, partial(np.percentile, q=#x1B[94m90#x1B[39;49;00m), #x1B[94mNone#x1B[39;49;00m, #x1B[94m0.03#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
            vmin=#x1B[94m0.01#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            vcenter=[#x1B[94m0.015#x1B[39;49;00m, #x1B[94mNone#x1B[39;49;00m, #x1B[94mNone#x1B[39;49;00m, #x1B[94m0.025#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
            outline_color=(#x1B[33m"#x1B[39;49;00m#x1B[33m#555555#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m0.9#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            outline_width=(#x1B[94m0.5#x1B[39;49;00m, #x1B[94m0.5#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            cmap=#x1B[33m"#x1B[39;49;00m#x1B[33mviridis_r#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            alpha=#x1B[94m0.9#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            wspace=#x1B[94m0.5#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            show=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtests/test_plotting.py#x1B[0m:1270: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[.../plotting/_tools/scatterplots.py#x1B[0m:344: in embedding
    #x1B[0mvmin_float, vmax_float, vcenter_float, norm_obj = _get_vboundnorm(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../plotting/_tools/scatterplots.py#x1B[0m:591: in _get_vboundnorm
    #x1B[0mlogg.error(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31msrc/scanpy/logging.py#x1B[0m:219: in error
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m settings._root_logger.error(msg, time=time, deep=deep, extra=extra)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Settings(preset=<Preset.ScanpyV1: 'scanpy-v1'>, verbosity=<Verbosity.hint: 3>, root_logger=<_RootLogger root (HINT)>, ...nore=frozenset({'?', 'N/A', 'dontknow', 'no_gate'}), logpath=PosixPath("<_io.FileIO name=12 mode='rb+' closefd=True>"))
item = '_root_logger'

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__getattr__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, item: #x1B[96mstr#x1B[39;49;00m) -> Any:#x1B[90m#x1B[39;49;00m
        private_attributes = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__private_attributes__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m private_attributes:#x1B[90m#x1B[39;49;00m
            attribute = private_attributes[item]#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(attribute, #x1B[33m'#x1B[39;49;00m#x1B[33m__get__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m attribute.#x1B[92m__get__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m))  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[90m# Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.__pydantic_private__[item]  #x1B[90m# type: ignore#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mKeyError#x1B[39;49;00m #x1B[94mas#x1B[39;49;00m exc:#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m) #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[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# `__pydantic_extra__` can fail to be set if the model is not yet fully initialized.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# See `BaseModel.__repr_args__` for more details#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[96mobject#x1B[39;49;00m.#x1B[92m__getattribute__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m'#x1B[39;49;00m#x1B[33m__pydantic_extra__#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94mexcept#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                pydantic_extra = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m pydantic_extra #x1B[95mand#x1B[39;49;00m item #x1B[95min#x1B[39;49;00m pydantic_extra:#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m pydantic_extra[item]#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m #x1B[96mhasattr#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m.#x1B[91m__class__#x1B[39;49;00m, item):#x1B[90m#x1B[39;49;00m
                    #x1B[94mreturn#x1B[39;49;00m #x1B[96msuper#x1B[39;49;00m().#x1B[92m__getattribute__#x1B[39;49;00m(item)  #x1B[90m# Raises AttributeError if appropriate#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    #x1B[90m# this is the current error#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>                   #x1B[94mraise#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m{#x1B[39;49;00m#x1B[96mtype#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m).#x1B[91m__name__#x1B[39;49;00m#x1B[33m!r}#x1B[39;49;00m#x1B[33m object has no attribute #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mitem#x1B[33m!r}#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE                   AttributeError: 'Settings' object has no attribute '_root_logger'. Did you mean: 'root_logger'?#x1B[0m

#x1B[1m#x1B[31m../../../..../scanpy/B9PcT7QG/hatch-test.few-extras/lib/python3.14.../site-packages/pydantic/main.py#x1B[0m:1042: AttributeError

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

@Zethson Zethson added this to the 1.12.2 milestone Jun 17, 2026
@ilan-gold

Copy link
Copy Markdown
Contributor

Hi @SID-6921 how does this differ from #4141? It seems like just having the numba kernels directly operate on the input is clean and fast. Thanks! Sorry for not replying sooner here!

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.

_sparse_nanmean is inefficient

4 participants