scverse-misc 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scverse_misc/__init__.py +3 -0
- scverse_misc/_extensions.py +265 -0
- scverse_misc/_pandas_utils.py +38 -0
- scverse_misc/_version.py +34 -0
- scverse_misc-0.0.1.dist-info/METADATA +106 -0
- scverse_misc-0.0.1.dist-info/RECORD +8 -0
- scverse_misc-0.0.1.dist-info/WHEEL +4 -0
- scverse_misc-0.0.1.dist-info/licenses/LICENSE +29 -0
scverse_misc/__init__.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import warnings
|
|
5
|
+
from itertools import islice
|
|
6
|
+
from typing import TYPE_CHECKING, Generic, Literal, Protocol, TypeVar, get_type_hints, overload, runtime_checkable
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from collections.abc import Callable, Set
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class ExtensionNamespace(Protocol):
|
|
14
|
+
"""Protocol for extension namespaces.
|
|
15
|
+
|
|
16
|
+
Enforces that the namespace initializer accepts a class with the proper `__init__` method.
|
|
17
|
+
Protocol's can't enforce that the `__init__` accepts the correct types. See
|
|
18
|
+
`_check_namespace_signature` for that. This is mainly useful for static type
|
|
19
|
+
checking with mypy and IDEs.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, instance) -> None:
|
|
23
|
+
"""Used to enforce the correct signature for extension namespaces."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Based off of the extension framework in Polars
|
|
27
|
+
# https://github.com/pola-rs/polars/blob/main/py-polars/polars/api.py
|
|
28
|
+
|
|
29
|
+
__all__ = ["make_register_namespace_decorator", "ExtensionNamespace"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
NameSpT = TypeVar("NameSpT", bound=ExtensionNamespace)
|
|
33
|
+
T = TypeVar("T")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AccessorNameSpace(ExtensionNamespace, Generic[NameSpT]):
|
|
37
|
+
"""Establish property-like namespace object for user-defined functionality."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, name: str, namespace: type[NameSpT]) -> None:
|
|
40
|
+
self._accessor = name
|
|
41
|
+
self._ns = namespace
|
|
42
|
+
|
|
43
|
+
@overload
|
|
44
|
+
def __get__(self, instance: None, cls: type[T]) -> type[NameSpT]: ...
|
|
45
|
+
|
|
46
|
+
@overload
|
|
47
|
+
def __get__(self, instance: T, cls: type[T]) -> NameSpT: ...
|
|
48
|
+
|
|
49
|
+
def __get__(self, instance: T | None, cls: type[T]) -> NameSpT | type[NameSpT]:
|
|
50
|
+
if instance is None:
|
|
51
|
+
return self._ns
|
|
52
|
+
|
|
53
|
+
ns_instance = self._ns(instance) # type: ignore[call-arg]
|
|
54
|
+
setattr(instance, self._accessor, ns_instance)
|
|
55
|
+
return ns_instance
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _check_namespace_signature(ns_class: type, cls: type, canonical_instance_name: str) -> None:
|
|
59
|
+
"""Validate the signature of a namespace class for extensions.
|
|
60
|
+
|
|
61
|
+
This function ensures that any class intended to be used as an extension namespace
|
|
62
|
+
has a properly formatted `__init__` method such that:
|
|
63
|
+
|
|
64
|
+
1. Accepts at least two parameters (self and the instance of the extended class)
|
|
65
|
+
2. Has `canonical_instance_name` as the name of the second parameter
|
|
66
|
+
3. Has the second parameter properly type-annotated as `ns_class` or any equivalent import alias
|
|
67
|
+
|
|
68
|
+
The function performs runtime validation of these requirements before a namespace
|
|
69
|
+
can be registered through the `register_namespace` decorator.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
ns_class: The namespace class to validate.
|
|
73
|
+
cls: The class that is being extended.
|
|
74
|
+
canonical_instance_name: The name of the `ns_class` constructor argument.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
TypeError: If the `__init__` method has fewer than 2 parameters (missing the instance parameter).
|
|
78
|
+
AttributeError: If the second parameter of `__init__` lacks a type annotation.
|
|
79
|
+
TypeError: If the second parameter of `__init__` is not named `canonical_instance_name`.
|
|
80
|
+
TypeError: If the second parameter of `__init__` is not annotated as the `ns_class` class.
|
|
81
|
+
TypeError: If both the name and type annotation of the second parameter are incorrect.
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
sig = inspect.signature(ns_class.__init__)
|
|
85
|
+
params = sig.parameters
|
|
86
|
+
|
|
87
|
+
# Ensure there are at least two parameters (self and mdata)
|
|
88
|
+
if len(params) < 2:
|
|
89
|
+
raise TypeError(f"Namespace initializer must accept a {cls.__name__} instance as the second parameter.")
|
|
90
|
+
|
|
91
|
+
# Get the second parameter (expected to be `canonical_instance_name`)
|
|
92
|
+
param = iter(params.values())
|
|
93
|
+
next(param)
|
|
94
|
+
param = next(param)
|
|
95
|
+
if param.annotation is inspect.Parameter.empty:
|
|
96
|
+
raise AttributeError(
|
|
97
|
+
f"Namespace initializer's second parameter must be annotated as the {cls.__name__!r} class, got empty annotation."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
name_ok = param.name == canonical_instance_name
|
|
101
|
+
|
|
102
|
+
# Resolve the annotation using get_type_hints to handle forward references and aliases.
|
|
103
|
+
try:
|
|
104
|
+
type_hints = get_type_hints(ns_class.__init__)
|
|
105
|
+
resolved_type = type_hints.get(param.name, param.annotation)
|
|
106
|
+
except NameError as e:
|
|
107
|
+
raise NameError(
|
|
108
|
+
f"Namespace initializer's second parameter must be named {canonical_instance_name!r}, got '{param.name}'."
|
|
109
|
+
) from e
|
|
110
|
+
|
|
111
|
+
type_ok = resolved_type is cls
|
|
112
|
+
|
|
113
|
+
match (name_ok, type_ok):
|
|
114
|
+
case (True, True):
|
|
115
|
+
return # Signature is correct.
|
|
116
|
+
case (False, True):
|
|
117
|
+
raise TypeError(
|
|
118
|
+
f"Namespace initializer's second parameter must be named {canonical_instance_name!r}, got {param.name!r}."
|
|
119
|
+
)
|
|
120
|
+
case (True, False):
|
|
121
|
+
type_repr = getattr(resolved_type, "__name__", str(resolved_type))
|
|
122
|
+
raise TypeError(
|
|
123
|
+
f"Namespace initializer's second parameter must be annotated as the {cls.__name__!r} class, got {type_repr!r}."
|
|
124
|
+
)
|
|
125
|
+
case _:
|
|
126
|
+
type_repr = getattr(resolved_type, "__name__", str(resolved_type))
|
|
127
|
+
raise TypeError(
|
|
128
|
+
f"Namespace initializer's second parameter must be named {canonical_instance_name!r}, got {param.name!r}. "
|
|
129
|
+
f"And must be annotated as {cls.__name__!r}, got {type_repr!r}."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _create_namespace(
|
|
134
|
+
name: str, cls: type, reserved_namespaces: Set[str], canonical_instance_name: str
|
|
135
|
+
) -> Callable[[type[NameSpT]], type[NameSpT]]:
|
|
136
|
+
"""Register custom namespace against the underlying class."""
|
|
137
|
+
|
|
138
|
+
def namespace(ns_class: type[NameSpT]) -> type[NameSpT]:
|
|
139
|
+
_check_namespace_signature(ns_class, cls, canonical_instance_name) # Perform the runtime signature check
|
|
140
|
+
if name in reserved_namespaces:
|
|
141
|
+
raise AttributeError(f"cannot override reserved attribute {name!r}")
|
|
142
|
+
elif hasattr(cls, name):
|
|
143
|
+
warnings.warn(
|
|
144
|
+
f"Overriding existing custom namespace {name!r} (on {cls.__name__!r})", UserWarning, stacklevel=2
|
|
145
|
+
)
|
|
146
|
+
setattr(cls, name, AccessorNameSpace(name, ns_class))
|
|
147
|
+
return ns_class
|
|
148
|
+
|
|
149
|
+
return namespace
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _indent_string_lines(string: str, indentation_level: int, skip_lines: int = 0):
|
|
153
|
+
minspace = 1e6
|
|
154
|
+
for line in islice(string.splitlines(), 1, None):
|
|
155
|
+
for i, char in enumerate(line):
|
|
156
|
+
if not char.isspace():
|
|
157
|
+
minspace = min(minspace, i)
|
|
158
|
+
break
|
|
159
|
+
if minspace == 1e6: # single-line string
|
|
160
|
+
minspace = 0
|
|
161
|
+
return "\n".join(
|
|
162
|
+
" " * 4 * indentation_level + sline if i >= skip_lines else sline
|
|
163
|
+
for i, line in enumerate(string.splitlines())
|
|
164
|
+
if (sline := (line[minspace:] if i > 0 else line)) or True
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def make_register_namespace_decorator(
|
|
169
|
+
cls: type, canonical_instance_name: str, decorator_name: str, docstring_style: Literal["google", "numpy"] = "google"
|
|
170
|
+
) -> Callable[[str], Callable[[type[NameSpT]], type[NameSpT]]]:
|
|
171
|
+
"""Create a decorator for registering custom functionality with a class.
|
|
172
|
+
|
|
173
|
+
The decorator will allow your users to extend `cls` objects with custom methods and properties
|
|
174
|
+
organized under a namespace. The namespace becomes accessible as an attribute on `cls` instances,
|
|
175
|
+
providing a clean way for users to add domain-specific functionality without modifying the `cls`
|
|
176
|
+
class itself.
|
|
177
|
+
|
|
178
|
+
The return decorator will have a docstring describing how to use it along with examples.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
cls: The class to be made extensible.
|
|
182
|
+
canonical_instance_name: The typical name of an instance of `cls`, e.g. `adata` for `AnnData`. This
|
|
183
|
+
is used for run-time checking of constructor signatures of the namespace classes.
|
|
184
|
+
decorator_name: The name under which the decorator is accessible in your package. This is used for
|
|
185
|
+
the examples in the decorator docstring.
|
|
186
|
+
docstring_style: Whether the docstring of the generated decorator should conform to NumPy or Google
|
|
187
|
+
style.
|
|
188
|
+
"""
|
|
189
|
+
# Reserved namespaces include accessors built into cls and all current attributes of cls
|
|
190
|
+
reserved_namespaces = set(dir(cls))
|
|
191
|
+
|
|
192
|
+
def decorator(name: str) -> Callable[[type[NameSpT]], type[NameSpT]]:
|
|
193
|
+
return _create_namespace(name, cls, reserved_namespaces, canonical_instance_name)
|
|
194
|
+
|
|
195
|
+
decorator_arg_description = f"""Name under which the accessor should be registered. This will be the attribute name
|
|
196
|
+
used to access your namespace's functionality on {cls.__name__} objects (e.g., `instance.name`).
|
|
197
|
+
Cannot conflict with existing {cls.__name__} attributes. The list of reserved attributes includes
|
|
198
|
+
everything outputted by `dir({cls.__name__})`."""
|
|
199
|
+
decorator_return_description = "A decorator that registers the decorated class as a custom namespace."
|
|
200
|
+
decorator_notes = f"""Implementation requirements:
|
|
201
|
+
|
|
202
|
+
1. The decorated class must have an `__init__` method that accepts exactly one parameter
|
|
203
|
+
(besides `self`) named `{canonical_instance_name}` and annotated with type :class:`~{cls.__module__}.{cls.__name__}`.
|
|
204
|
+
2. The namespace will be initialized with the {cls.__name__} object on first access and then
|
|
205
|
+
cached on the instance.
|
|
206
|
+
3. If the namespace name conflicts with an existing namespace, a warning is issued.
|
|
207
|
+
4. If the namespace name conflicts with a built-in {cls.__name__} attribute, an AttributeError is raised."""
|
|
208
|
+
decorator_examples = f""">>> @{decorator_name}("do_something")
|
|
209
|
+
... class DoSomething:
|
|
210
|
+
... def __init__(self, {canonical_instance_name}: {cls.__name__}):
|
|
211
|
+
... self._obj = {canonical_instance_name}
|
|
212
|
+
...
|
|
213
|
+
... def has_foo(self) -> bool:
|
|
214
|
+
... return hasattr(self._obj, "foo")
|
|
215
|
+
>>>
|
|
216
|
+
>>> # Create a {cls.__name__} object
|
|
217
|
+
>>> obj = {cls.__name__}()
|
|
218
|
+
>>>
|
|
219
|
+
>>> # use the registered namespace
|
|
220
|
+
>>> obj.do_something.has_foo()
|
|
221
|
+
False"""
|
|
222
|
+
|
|
223
|
+
decorator.__doc__ = f"""Decorator for registering custom functionality with a :class:`~{cls.__module__}.{cls.__name__}` object.
|
|
224
|
+
|
|
225
|
+
This decorator allows you to extend {cls.__name__} objects with custom methods and properties
|
|
226
|
+
organized under a namespace. The namespace becomes accessible as an attribute on {cls.__name__}
|
|
227
|
+
instances, providing a clean way to you to add domain-specific functionality without modifying
|
|
228
|
+
the {cls.__name__} class itself, or extending the class with additional methods as you see fit in your workflow.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
if docstring_style == "google":
|
|
232
|
+
decorator.__doc__ += f"""
|
|
233
|
+
Args:
|
|
234
|
+
name: {_indent_string_lines(decorator_arg_description, 3, 1)}
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
{_indent_string_lines(decorator_return_description, 2, 1)}
|
|
238
|
+
|
|
239
|
+
Notes:
|
|
240
|
+
{_indent_string_lines(decorator_notes, 2, 1)}
|
|
241
|
+
|
|
242
|
+
Examples:
|
|
243
|
+
{_indent_string_lines(decorator_examples, 2, 1)}
|
|
244
|
+
"""
|
|
245
|
+
else:
|
|
246
|
+
decorator.__doc__ += f"""
|
|
247
|
+
Parameters
|
|
248
|
+
----------
|
|
249
|
+
name
|
|
250
|
+
{_indent_string_lines(decorator_arg_description, 2, 1)}
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
{_indent_string_lines(decorator_return_description, 1, 1)}
|
|
255
|
+
|
|
256
|
+
Notes
|
|
257
|
+
-----
|
|
258
|
+
{_indent_string_lines(decorator_notes, 1, 1)}
|
|
259
|
+
|
|
260
|
+
Examples
|
|
261
|
+
--------
|
|
262
|
+
{_indent_string_lines(decorator_examples, 1, 1)}
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
return decorator
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from contextlib import suppress
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def try_convert_series_to_numpy_dtype(col: pd.Series) -> pd.Series:
|
|
8
|
+
"""Attempt to convert a :class:`~pandas.Series` to a non-nullable dtype.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
col: The series to be converted.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
The converted series, `col` did not contain any :data:`~pandas.NA` values, the unmodified `col` otherwise.
|
|
15
|
+
"""
|
|
16
|
+
with suppress(ValueError):
|
|
17
|
+
match col.dtype:
|
|
18
|
+
case pd.BooleanDtype():
|
|
19
|
+
col = col.astype(bool)
|
|
20
|
+
case pd.core.arrays.integer.IntegerDtype(type=dtype) | pd.core.arrays.floating.FloatingDtype(type=dtype):
|
|
21
|
+
col = col.astype(dtype)
|
|
22
|
+
return col
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def try_convert_dataframe_to_numpy_dtypes(df: pd.DataFrame | Mapping[str, pd.Series]) -> pd.DataFrame:
|
|
26
|
+
"""Attempt to convert all columns of a :class:`~pandas.DataFrame` to their respective non-nullable dtype.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
df: The dataframe to be converted.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
A new dataframe with each column of `df` that had a nullable dtype but did not contain any :data:`~pandas.NA`
|
|
33
|
+
values converted to the corresponding non-nullable dtype.
|
|
34
|
+
"""
|
|
35
|
+
new_cols = {}
|
|
36
|
+
for colname, col in df.items():
|
|
37
|
+
new_cols[colname] = try_convert_series_to_numpy_dtype(col)
|
|
38
|
+
return pd.DataFrame(new_cols)
|
scverse_misc/_version.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.0.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 1)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scverse-misc
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Miscellaneous utility code used by scverse packages
|
|
5
|
+
Project-URL: Documentation, https://scverse-misc.readthedocs.io/
|
|
6
|
+
Project-URL: Homepage, https://github.com/scverse/scverse-misc
|
|
7
|
+
Project-URL: Source, https://github.com/scverse/scverse-misc
|
|
8
|
+
Author: Ilia Kats
|
|
9
|
+
Maintainer-email: Ilia Kats <i.kats@dkfz.de>
|
|
10
|
+
License: BSD 3-Clause License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026, Ilia Kats
|
|
13
|
+
All rights reserved.
|
|
14
|
+
|
|
15
|
+
Redistribution and use in source and binary forms, with or without
|
|
16
|
+
modification, are permitted provided that the following conditions are met:
|
|
17
|
+
|
|
18
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
19
|
+
list of conditions and the following disclaimer.
|
|
20
|
+
|
|
21
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
22
|
+
this list of conditions and the following disclaimer in the documentation
|
|
23
|
+
and/or other materials provided with the distribution.
|
|
24
|
+
|
|
25
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
26
|
+
contributors may be used to endorse or promote products derived from
|
|
27
|
+
this software without specific prior written permission.
|
|
28
|
+
|
|
29
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
30
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
31
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
32
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
33
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
34
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
35
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
36
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
37
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
38
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
45
|
+
Requires-Python: >=3.11
|
|
46
|
+
Requires-Dist: pandas>=1
|
|
47
|
+
Requires-Dist: session-info2
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# scverse-misc
|
|
51
|
+
|
|
52
|
+
[![Tests][badge-tests]][tests]
|
|
53
|
+
[![codecov][badge-codecov]][codecov]
|
|
54
|
+
[![Documentation][badge-docs]][documentation]
|
|
55
|
+
|
|
56
|
+
[badge-tests]: https://github.com/scverse/scverse-misc/actions/workflows/test.yaml/badge.svg
|
|
57
|
+
[badge-codecov]: https://codecov.io/gh/scverse/scverse-misc/graph/badge.svg?token=EUH9BZZK7T
|
|
58
|
+
[badge-docs]: https://img.shields.io/readthedocs/scverse-misc
|
|
59
|
+
|
|
60
|
+
Miscellaneous utility code used by scverse packages
|
|
61
|
+
|
|
62
|
+
## Getting started
|
|
63
|
+
|
|
64
|
+
Please refer to the [documentation][],
|
|
65
|
+
in particular, the [API documentation][].
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
You need to have Python 3.11 or newer installed on your system.
|
|
70
|
+
If you don't have Python installed, we recommend installing [uv][].
|
|
71
|
+
|
|
72
|
+
There are several alternative options to install scverse-misc:
|
|
73
|
+
|
|
74
|
+
<!--
|
|
75
|
+
1) Install the latest release of `scverse-misc` from [PyPI][]:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install scverse-misc
|
|
79
|
+
```
|
|
80
|
+
-->
|
|
81
|
+
|
|
82
|
+
1. Install the latest development version:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install git+https://github.com/scverse/scverse-misc.git@main
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Release notes
|
|
89
|
+
|
|
90
|
+
See the [changelog][].
|
|
91
|
+
|
|
92
|
+
## Contact
|
|
93
|
+
|
|
94
|
+
For questions and help requests, you can reach out in the [scverse discourse][].
|
|
95
|
+
If you found a bug, please use the [issue tracker][].
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
[uv]: https://github.com/astral-sh/uv
|
|
99
|
+
[scverse discourse]: https://discourse.scverse.org/
|
|
100
|
+
[issue tracker]: https://github.com/scverse/scverse-misc/issues
|
|
101
|
+
[tests]: https://github.com/scverse/scverse-misc/actions/workflows/test.yaml
|
|
102
|
+
[codecov]: https://codecov.io/gh/bioFAM/mofaflex
|
|
103
|
+
[documentation]: https://scverse-misc.readthedocs.io
|
|
104
|
+
[changelog]: https://scverse-misc.readthedocs.io/en/latest/changelog.html
|
|
105
|
+
[api documentation]: https://scverse-misc.readthedocs.io/latest/api.html
|
|
106
|
+
[pypi]: https://pypi.org/project/scverse-misc
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
scverse_misc/__init__.py,sha256=xAuMYz1eRg2x5oruK1Jps-OwDUnoIapCGUWMQwaF_AQ,232
|
|
2
|
+
scverse_misc/_extensions.py,sha256=Rjss5vJe9nmwwBZWtL56qsIQ1vjr6ZtxZXJbYoLfWx4,11294
|
|
3
|
+
scverse_misc/_pandas_utils.py,sha256=rZ_POYn0bxb6FnIl418T1vzxEEFtarwt1TR5KEf94Sg,1359
|
|
4
|
+
scverse_misc/_version.py,sha256=qf6R-J7-UyuABBo8c0HgaquJ8bejVbf07HodXgwAwgQ,704
|
|
5
|
+
scverse_misc-0.0.1.dist-info/METADATA,sha256=NFbv_M2qoiMvoSYeirAxw4MYdl4pft1zDthJ19psRYk,4219
|
|
6
|
+
scverse_misc-0.0.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
7
|
+
scverse_misc-0.0.1.dist-info/licenses/LICENSE,sha256=gjAb5KNAlstoHZ6PFN5lH7j_DUpda5-FfdwxlIqjDt0,1517
|
|
8
|
+
scverse_misc-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Ilia Kats
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|