scmcp-shared 0.1.0__tar.gz

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.
@@ -0,0 +1,49 @@
1
+ name: Publishing
2
+ on:
3
+ release:
4
+ types: [published]
5
+
6
+ jobs:
7
+ release-build:
8
+ name: Build distribution
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+
13
+ - name: Install uv
14
+ uses: astral-sh/setup-uv@v5
15
+ with:
16
+ enable-cache: true
17
+
18
+ - name: "Set up Python"
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version-file: "pyproject.toml"
22
+
23
+ - name: Build
24
+ run: uv build
25
+
26
+ - name: Upload artifacts
27
+ uses: actions/upload-artifact@v4
28
+ with:
29
+ name: release-dists
30
+ path: dist/
31
+
32
+ pypi-publish:
33
+ name: Upload release to PyPI
34
+ runs-on: ubuntu-latest
35
+ environment: pypi
36
+ needs:
37
+ - release-build
38
+ permissions:
39
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
40
+
41
+ steps:
42
+ - name: Retrieve release distributions
43
+ uses: actions/download-artifact@v4
44
+ with:
45
+ name: release-dists
46
+ path: dist/
47
+
48
+ - name: Publish package distributions to PyPI
49
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, scmcphub
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: scmcp_shared
3
+ Version: 0.1.0
4
+ Summary: A shared function libray for scmcphub
5
+ Author-email: shuang <hsh-me@outlook.com>
6
+ License: BSD 3-Clause License
7
+
8
+ Copyright (c) 2025, scmcphub
9
+
10
+ Redistribution and use in source and binary forms, with or without
11
+ modification, are permitted provided that the following conditions are met:
12
+
13
+ 1. Redistributions of source code must retain the above copyright notice, this
14
+ list of conditions and the following disclaimer.
15
+
16
+ 2. Redistributions in binary form must reproduce the above copyright notice,
17
+ this list of conditions and the following disclaimer in the documentation
18
+ and/or other materials provided with the distribution.
19
+
20
+ 3. Neither the name of the copyright holder nor the names of its
21
+ contributors may be used to endorse or promote products derived from
22
+ this software without specific prior written permission.
23
+
24
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
28
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ License-File: LICENSE
35
+ Requires-Python: >=3.10
36
+ Requires-Dist: fastmcp>=2.3.0
37
+ Requires-Dist: mcp>=1.8.0
38
+ Requires-Dist: pydantic
39
+ Requires-Dist: scanpy
40
+ Description-Content-Type: text/markdown
41
+
42
+ # scmcp-shared
43
+
44
+ Shared functions for scmcphub
@@ -0,0 +1,3 @@
1
+ # scmcp-shared
2
+
3
+ Shared functions for scmcphub
@@ -0,0 +1,33 @@
1
+ [project]
2
+ name = "scmcp_shared"
3
+ dynamic = ["version"]
4
+ description = "A shared function libray for scmcphub"
5
+ readme = "README.md"
6
+ license = { file = "LICENSE" }
7
+ authors = [
8
+ { name = "shuang", email = "hsh-me@outlook.com" }
9
+ ]
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "scanpy",
13
+ "mcp>=1.8.0",
14
+ "fastmcp>=2.3.0",
15
+ "pydantic",
16
+ ]
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
21
+
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "pytest>=8.3.4",
26
+ ]
27
+
28
+ [tool.hatch.version]
29
+ path = "src/scmcp_shared/__init__.py"
30
+
31
+ [tool.pytest.ini_options]
32
+ asyncio_mode = "strict"
33
+ asyncio_default_fixture_loop_scope = "function"
@@ -0,0 +1,3 @@
1
+
2
+ __version__ = "0.1.0"
3
+
@@ -0,0 +1,31 @@
1
+ import logging
2
+ import sys
3
+ import os
4
+ from .util import get_env
5
+
6
+ def setup_logger(name="sc-mcp-server", log_file=None):
7
+
8
+ logger = logging.getLogger(name)
9
+ logger.setLevel(logging.INFO)
10
+ if logger.handlers:
11
+ return logger
12
+
13
+ formatter = logging.Formatter(
14
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
15
+ '%Y-%m-%d %H:%M:%S'
16
+ )
17
+ if log_file is None:
18
+ log_file = get_env("LOG_FILE")
19
+ if log_file:
20
+ log_handler = logging.FileHandler(log_file)
21
+ log_handler.setFormatter(formatter)
22
+ logger.addHandler(log_handler)
23
+
24
+ logger.info(f"logging output: {log_file}")
25
+ else:
26
+ log_handler = logging.StreamHandler(sys.stdout)
27
+ log_handler.setFormatter(formatter)
28
+ logger.addHandler(log_handler)
29
+ logger.info(f"loggin file output: stdout")
30
+ return logger
31
+
@@ -0,0 +1 @@
1
+ from .io import *
@@ -0,0 +1,120 @@
1
+ from pydantic import (
2
+ Field,
3
+ ValidationInfo,
4
+ computed_field,
5
+ field_validator,
6
+ model_validator,
7
+ BaseModel
8
+ )
9
+ from typing import Optional, Union, Literal, Any, Sequence, Dict
10
+
11
+
12
+
13
+ class ReadModel(BaseModel):
14
+ """Input schema for the read tool."""
15
+ filename: str = Field(
16
+ ...,
17
+ description="Path to the file to read."
18
+ )
19
+
20
+ backed: Literal['r', 'r+'] = Field(
21
+ default=None,
22
+ description="If 'r', load AnnData in 'backed' mode instead of fully loading it into memory ('memory' mode). If you want to modify backed attributes of the AnnData object, you need to choose 'r+'."
23
+ )
24
+ sheet: str = Field(
25
+ default=None,
26
+ description="Name of sheet/table in hdf5 or Excel file."
27
+ )
28
+ ext: str = Field(
29
+ default=None,
30
+ description="Extension that indicates the file type. If None, uses extension of filename."
31
+ )
32
+ delimiter: str = Field(
33
+ default=None,
34
+ description="Delimiter that separates data within text file. If None, will split at arbitrary number of white spaces, which is different from enforcing splitting at any single white space."
35
+ )
36
+ first_column_names: bool = Field(
37
+ default=False,
38
+ description="Assume the first column stores row names. This is only necessary if these are not strings: strings in the first column are automatically assumed to be row names."
39
+ )
40
+ first_column_obs: bool = Field(
41
+ default=True,
42
+ description="If True, assume the first column stores observations (cell or barcode) names when provide text file. If False, the data will be transposed."
43
+ )
44
+ backup_url: str = Field(
45
+ default=None,
46
+ description="Retrieve the file from an URL if not present on disk."
47
+ )
48
+ cache: bool = Field(
49
+ default=False,
50
+ description="If False, read from source, if True, read from fast 'h5ad' cache."
51
+ )
52
+ cache_compression: Literal['gzip', 'lzf'] = Field(
53
+ default=None,
54
+ description="See the h5py dataset_compression. (Default: settings.cache_compression)"
55
+ )
56
+ var_names: str = Field(
57
+ default="gene_symbols",
58
+ description="The variables index for 10x mtx format. Either 'gene_symbols' or 'gene_ids'."
59
+ )
60
+ make_unique: bool = Field(
61
+ default=True,
62
+ description="Whether to make the variables index unique by appending '-1', '-2' etc. or not. Used for 10x mtx format."
63
+ )
64
+ gex_only: bool = Field(
65
+ default=True,
66
+ description="Only keep 'Gene Expression' data and ignore other feature types, e.g. 'Antibody Capture', 'CRISPR Guide Capture', or 'Custom'. Used for 10x formats."
67
+ )
68
+ prefix: str = Field(
69
+ default=None,
70
+ description="Any prefix before matrix.mtx, genes.tsv and barcodes.tsv. For instance, if the files are named patientA_matrix.mtx, patientA_genes.tsv and patientA_barcodes.tsv the prefix is patientA_. Used for 10x mtx format."
71
+ )
72
+
73
+ @field_validator('backed')
74
+ def validate_backed(cls, v: Optional[str]) -> Optional[str]:
75
+ if v is not None and v not in ['r', 'r+']:
76
+ raise ValueError("If backed is provided, it must be either 'r' or 'r+'")
77
+ return v
78
+
79
+ @field_validator('cache_compression')
80
+ def validate_cache_compression(cls, v: Optional[str]) -> Optional[str]:
81
+ if v is not None and v not in ['gzip', 'lzf']:
82
+ raise ValueError("cache_compression must be either 'gzip', 'lzf', or None")
83
+ return v
84
+
85
+ @field_validator('var_names')
86
+ def validate_var_names(cls, v: Optional[str]) -> Optional[str]:
87
+ if v is not None and v not in ['gene_symbols', 'gene_ids']:
88
+ raise ValueError("var_names must be either 'gene_symbols' or 'gene_ids'")
89
+ return v
90
+
91
+
92
+ class WriteModel(BaseModel):
93
+ """Input schema for the write tool."""
94
+ filename: str = Field(
95
+ description="Path to save the file. If no extension is provided, the default format will be used."
96
+ )
97
+ ext: Literal['h5', 'csv', 'txt', 'npz'] = Field(
98
+ default=None,
99
+ description="File extension to infer file format. If None, defaults to scanpy's settings.file_format_data."
100
+ )
101
+ compression: Literal['gzip', 'lzf'] = Field(
102
+ default='gzip',
103
+ description="Compression format for h5 files."
104
+ )
105
+ compression_opts: int = Field(
106
+ default=None,
107
+ description="Compression options for h5 files."
108
+ )
109
+
110
+ @field_validator('filename')
111
+ def validate_filename(cls, v: str) -> str:
112
+ # Allow any filename since the extension is optional and can be inferred
113
+ return v
114
+
115
+ @model_validator(mode='after')
116
+ def validate_extension_compression(self) -> 'WriteInput':
117
+ # If ext is provided and not h5, compression should be None
118
+ if self.ext is not None and self.ext != 'h5' and self.compression is not None:
119
+ raise ValueError("Compression can only be used with h5 files")
120
+ return self