aabatlas 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,20 @@
1
+ data/
2
+
3
+ # Python
4
+ __pycache__/
5
+ *.pyc
6
+ *.egg-info/
7
+ /python-package/dist/
8
+ /python-package/build/
9
+ .pytest_cache/
10
+
11
+ # R
12
+ .Rhistory
13
+ .RData
14
+ .Rproj.user/
15
+ *.Rcheck/
16
+ *.tar.gz
17
+
18
+ # OS
19
+ .DS_Store
20
+ Thumbs.db
aabatlas-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexandre Marques
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to
7
+ deal in the Software without restriction, including without limitation the
8
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9
+ sell copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+ DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.5
2
+ Name: aabatlas
3
+ Version: 0.1.0
4
+ Summary: Client for the antibodyome-atlas, a curated catalog of autoantibody profiling datasets and papers
5
+ Project-URL: Homepage, https://github.com/ahcm088/antibodyome-atlas
6
+ Project-URL: Issues, https://github.com/ahcm088/antibodyome-atlas/issues
7
+ Author-email: Alexandre Marques <marquesufcg@gmail.com>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: ArrayExpress,GEO,autoantibody,bioinformatics,proteomics
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
16
+ Requires-Python: >=3.9
17
+ Requires-Dist: pandas>=1.5
18
+ Requires-Dist: requests>=2.31
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=7.0; extra == 'test'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # aabatlas
24
+
25
+ Python client for the [antibodyome-atlas](https://github.com/ahcm088/antibodyome-atlas), a curated catalog of autoantibody profiling datasets and papers. See `schema/README.md` in the atlas repo for the data model. Mirrors the R client, [AAbAtlas](../r-package).
26
+
27
+ ## Install
28
+
29
+ Not yet on PyPI. Until then, install from GitHub:
30
+
31
+ ```bash
32
+ pip install "git+https://github.com/ahcm088/antibodyome-atlas.git#subdirectory=python-package"
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ```python
38
+ import aabatlas
39
+
40
+ # browse
41
+ ds = aabatlas.list_datasets()
42
+ ds[(ds["record_type"] == "DATASET") & (ds["total_sample_n"] > 100)][["atlas_id", "title", "total_sample_n"]]
43
+
44
+ # one record, with its publication and platforms resolved
45
+ rec = aabatlas.get_dataset("AAB-000004")
46
+ rec["title"]
47
+ rec["publication"]["citation"]
48
+
49
+ # citation ready to paste into a manuscript
50
+ aabatlas.get_citation("AAB-000004")
51
+
52
+ # download the underlying file
53
+ aabatlas.download("AAB-000004")
54
+ ```
55
+
56
+ By default every function reads the latest curated data (`ref="main"`). Pin a release tag for reproducibility in a publication:
57
+
58
+ ```python
59
+ aabatlas.list_datasets(ref="v1.0.0")
60
+ ```
@@ -0,0 +1,38 @@
1
+ # aabatlas
2
+
3
+ Python client for the [antibodyome-atlas](https://github.com/ahcm088/antibodyome-atlas), a curated catalog of autoantibody profiling datasets and papers. See `schema/README.md` in the atlas repo for the data model. Mirrors the R client, [AAbAtlas](../r-package).
4
+
5
+ ## Install
6
+
7
+ Not yet on PyPI. Until then, install from GitHub:
8
+
9
+ ```bash
10
+ pip install "git+https://github.com/ahcm088/antibodyome-atlas.git#subdirectory=python-package"
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```python
16
+ import aabatlas
17
+
18
+ # browse
19
+ ds = aabatlas.list_datasets()
20
+ ds[(ds["record_type"] == "DATASET") & (ds["total_sample_n"] > 100)][["atlas_id", "title", "total_sample_n"]]
21
+
22
+ # one record, with its publication and platforms resolved
23
+ rec = aabatlas.get_dataset("AAB-000004")
24
+ rec["title"]
25
+ rec["publication"]["citation"]
26
+
27
+ # citation ready to paste into a manuscript
28
+ aabatlas.get_citation("AAB-000004")
29
+
30
+ # download the underlying file
31
+ aabatlas.download("AAB-000004")
32
+ ```
33
+
34
+ By default every function reads the latest curated data (`ref="main"`). Pin a release tag for reproducibility in a publication:
35
+
36
+ ```python
37
+ aabatlas.list_datasets(ref="v1.0.0")
38
+ ```
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "aabatlas"
7
+ version = "0.1.0"
8
+ description = "Client for the antibodyome-atlas, a curated catalog of autoantibody profiling datasets and papers"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Alexandre Marques", email = "marquesufcg@gmail.com" }]
13
+ keywords = ["autoantibody", "proteomics", "bioinformatics", "GEO", "ArrayExpress"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Science/Research",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
20
+ ]
21
+ dependencies = [
22
+ "requests>=2.31",
23
+ "pandas>=1.5",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ test = ["pytest>=7.0"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/ahcm088/antibodyome-atlas"
31
+ Issues = "https://github.com/ahcm088/antibodyome-atlas/issues"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/aabatlas"]
@@ -0,0 +1,18 @@
1
+ """Client for the antibodyome-atlas."""
2
+ from ._source import raw_url
3
+ from .citation import get_citation
4
+ from .datasets import get_dataset, list_datasets, list_link_status, list_platforms, list_publications
5
+ from .download import download
6
+
7
+ __version__ = "0.1.0"
8
+
9
+ __all__ = [
10
+ "list_datasets",
11
+ "list_publications",
12
+ "list_platforms",
13
+ "list_link_status",
14
+ "get_dataset",
15
+ "get_citation",
16
+ "download",
17
+ "raw_url",
18
+ ]
@@ -0,0 +1,40 @@
1
+ """Internal: fetch and parse the atlas's GitHub-hosted JSON manifests."""
2
+ from __future__ import annotations
3
+
4
+ import pandas as pd
5
+ import requests
6
+
7
+ _FILES = ("datasets", "publications", "platforms", "link_status", "seeds")
8
+ _USER_AGENT = "aabatlas-python/0.1 (+https://github.com/ahcm088/antibodyome-atlas)"
9
+
10
+
11
+ def raw_url(file: str, ref: str = "main") -> str:
12
+ """Build the raw GitHub URL for one of the atlas's metadata files.
13
+
14
+ Parameters
15
+ ----------
16
+ file:
17
+ One of "datasets", "publications", "platforms", "link_status", "seeds".
18
+ ref:
19
+ Git ref (branch, tag, or commit SHA) to read from. Defaults to
20
+ "main" (the latest curated data). Pin to a release tag (e.g.
21
+ "v1.0.0") for reproducible results in a publication.
22
+ """
23
+ if file not in _FILES:
24
+ raise ValueError(f"file must be one of {_FILES}, got {file!r}")
25
+ return f"https://raw.githubusercontent.com/ahcm088/antibodyome-atlas/{ref}/metadata/{file}.json"
26
+
27
+
28
+ def fetch(file: str, ref: str = "main") -> pd.DataFrame:
29
+ """Fetch and parse one of the atlas's JSON manifests as a DataFrame.
30
+
31
+ Nested fields (e.g. ``conditions``, ``platforms``, ``source_ids``) come
32
+ back as object-dtype columns holding Python lists/dicts.
33
+ """
34
+ url = raw_url(file, ref=ref)
35
+ resp = requests.get(url, timeout=30, headers={"User-Agent": _USER_AGENT})
36
+ resp.raise_for_status()
37
+ data = resp.json()
38
+ if not data:
39
+ return pd.DataFrame()
40
+ return pd.DataFrame(data)
@@ -0,0 +1,25 @@
1
+ """Citation helper."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any
5
+
6
+ from .datasets import get_dataset
7
+
8
+
9
+ def get_citation(atlas_id: str, ref: str = "main") -> dict[str, Any]:
10
+ """Get the citation for a dataset, ready to paste into a manuscript.
11
+
12
+ Returns a dict with ``atlas_id``, ``dataset_link``, ``citation``,
13
+ ``doi``, ``pubmed_link``, ``publisher_link``. Publication fields are
14
+ ``None`` if the record has no associated publication.
15
+ """
16
+ record = get_dataset(atlas_id, ref=ref)
17
+ pub = record.get("publication") or {}
18
+ return {
19
+ "atlas_id": record["atlas_id"],
20
+ "dataset_link": record["dataset_link"],
21
+ "citation": pub.get("citation"),
22
+ "doi": pub.get("doi"),
23
+ "pubmed_link": pub.get("pubmed_link"),
24
+ "publisher_link": pub.get("publisher_link"),
25
+ }
@@ -0,0 +1,84 @@
1
+ """List and resolve atlas records."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any
5
+
6
+ import pandas as pd
7
+
8
+ from ._source import fetch
9
+
10
+
11
+ def list_datasets(ref: str = "main") -> pd.DataFrame:
12
+ """List all datasets/papers in the atlas.
13
+
14
+ Columns match ``schema/dataset.schema.json`` in the atlas repo
15
+ (``atlas_id``, ``record_type``, ``title``, ``organism``, ``conditions``,
16
+ ``total_sample_n``, ``platforms``, ``dataset_link``,
17
+ ``curated_availability``, etc.). List/dict-valued fields come back as
18
+ Python objects in object-dtype columns.
19
+ """
20
+ return fetch("datasets", ref=ref)
21
+
22
+
23
+ def list_publications(ref: str = "main") -> pd.DataFrame:
24
+ """List all publications referenced by atlas datasets."""
25
+ return fetch("publications", ref=ref)
26
+
27
+
28
+ def list_platforms(ref: str = "main") -> pd.DataFrame:
29
+ """List all assay platforms referenced by atlas datasets.
30
+
31
+ Per-study protein/feature counts live on the dataset record
32
+ (``list_datasets()["platforms"]``), not here -- see ``schema/README.md``
33
+ in the atlas repo for why.
34
+ """
35
+ return fetch("platforms", ref=ref)
36
+
37
+
38
+ def list_link_status(ref: str = "main") -> pd.DataFrame:
39
+ """List the link-reachability check history.
40
+
41
+ One row per automated check, appended over time by the atlas's
42
+ scheduled link checker. ``reachable`` is the raw HTTP signal;
43
+ ``likely_bot_blocked`` flags 403/429 responses from publisher anti-bot
44
+ defenses, which are not necessarily dead links.
45
+ """
46
+ return fetch("link_status", ref=ref)
47
+
48
+
49
+ def get_dataset(atlas_id: str, ref: str = "main") -> dict[str, Any]:
50
+ """Get a single dataset record, with its publication and platforms resolved.
51
+
52
+ Returns a dict with the dataset's fields plus two resolved keys:
53
+ ``publication`` (a dict, or ``None`` if the record has no associated
54
+ publication) and ``platforms_detail`` (a list combining each
55
+ ``platforms[].platform_id`` from the dataset with the matching row from
56
+ ``list_platforms()``, keeping ``n_proteins_reported``).
57
+ """
58
+ ds = list_datasets(ref=ref)
59
+ match = ds[ds["atlas_id"] == atlas_id]
60
+ if match.empty:
61
+ raise ValueError(f"No dataset found with atlas_id: {atlas_id}")
62
+ record = match.iloc[0].to_dict()
63
+
64
+ pub_id = record.get("publication_id")
65
+ if pub_id:
66
+ pubs = list_publications(ref=ref)
67
+ pub_match = pubs[pubs["publication_id"] == pub_id]
68
+ record["publication"] = pub_match.iloc[0].to_dict() if not pub_match.empty else None
69
+ else:
70
+ record["publication"] = None
71
+
72
+ platforms = record.get("platforms") or []
73
+ detail = []
74
+ if platforms:
75
+ all_platforms = list_platforms(ref=ref)
76
+ for p in platforms:
77
+ merged = dict(p)
78
+ prow = all_platforms[all_platforms["platform_id"] == p["platform_id"]]
79
+ if not prow.empty:
80
+ merged.update({k: v for k, v in prow.iloc[0].to_dict().items() if k != "platform_id"})
81
+ detail.append(merged)
82
+ record["platforms_detail"] = detail
83
+
84
+ return record
@@ -0,0 +1,48 @@
1
+ """Download a dataset's data file."""
2
+ from __future__ import annotations
3
+
4
+ import warnings
5
+ from pathlib import Path
6
+ from urllib.parse import urlsplit
7
+
8
+ import requests
9
+
10
+ from .datasets import get_dataset
11
+
12
+ _USER_AGENT = "aabatlas-python/0.1 (+https://github.com/ahcm088/antibodyome-atlas)"
13
+
14
+
15
+ def download(atlas_id: str, destdir: str = ".", destfile: str | None = None, ref: str = "main") -> Path:
16
+ """Download whatever is at the record's ``dataset_link``.
17
+
18
+ For records with ``record_type == "DATASET"`` this is usually a
19
+ repository landing page (e.g. a GEO accession page) rather than a
20
+ direct file: check ``curated_availability`` first, and note that some
21
+ repositories require navigating from that page to the actual
22
+ supplementary files by hand.
23
+
24
+ Returns the local file path.
25
+ """
26
+ record = get_dataset(atlas_id, ref=ref)
27
+
28
+ status = (record.get("curated_availability") or {}).get("data_status")
29
+ if status in ("unavailable", "upon_request"):
30
+ warnings.warn(
31
+ f"curated_availability.data_status for {atlas_id} is {status!r} -- "
32
+ "the data itself may not be directly downloadable. Proceeding to fetch dataset_link anyway."
33
+ )
34
+
35
+ url = record["dataset_link"]
36
+ if destfile is None:
37
+ destfile = Path(urlsplit(url).path).name or f"{atlas_id}.html"
38
+
39
+ path = Path(destdir) / destfile
40
+ path.parent.mkdir(parents=True, exist_ok=True)
41
+
42
+ resp = requests.get(url, timeout=60, headers={"User-Agent": _USER_AGENT}, stream=True)
43
+ resp.raise_for_status()
44
+ with open(path, "wb") as f:
45
+ for chunk in resp.iter_content(chunk_size=8192):
46
+ f.write(chunk)
47
+
48
+ return path
@@ -0,0 +1,15 @@
1
+ import pytest
2
+ import requests
3
+
4
+ from aabatlas._source import raw_url
5
+
6
+
7
+ @pytest.fixture
8
+ def atlas_or_skip():
9
+ try:
10
+ resp = requests.get(raw_url("datasets"), timeout=10)
11
+ ok = resp.status_code == 200
12
+ except requests.RequestException:
13
+ ok = False
14
+ if not ok:
15
+ pytest.skip("metadata/datasets.json not reachable at ref 'main' (repo not pushed yet, or offline)")
@@ -0,0 +1,39 @@
1
+ import pytest
2
+
3
+ import aabatlas
4
+
5
+
6
+ def test_raw_url_builds_the_expected_url():
7
+ assert aabatlas.raw_url("datasets", ref="v1.0.0") == (
8
+ "https://raw.githubusercontent.com/ahcm088/antibodyome-atlas/v1.0.0/metadata/datasets.json"
9
+ )
10
+ with pytest.raises(ValueError):
11
+ aabatlas.raw_url("nope")
12
+
13
+
14
+ def test_list_datasets_returns_expected_columns(atlas_or_skip):
15
+ ds = aabatlas.list_datasets()
16
+ assert len(ds) > 0
17
+ for col in ("atlas_id", "record_type", "title", "dataset_link"):
18
+ assert col in ds.columns
19
+
20
+
21
+ def test_get_dataset_resolves_publication_and_platforms(atlas_or_skip):
22
+ ds = aabatlas.list_datasets()
23
+ target = ds[ds["publication_id"].notna()]["atlas_id"].iloc[0]
24
+ record = aabatlas.get_dataset(target)
25
+ assert record["atlas_id"] == target
26
+ assert record["publication"] is not None
27
+
28
+
29
+ def test_get_dataset_raises_on_unknown_atlas_id(atlas_or_skip):
30
+ with pytest.raises(ValueError, match="No dataset found"):
31
+ aabatlas.get_dataset("AAB-999999")
32
+
33
+
34
+ def test_get_citation_returns_expected_keys(atlas_or_skip):
35
+ ds = aabatlas.list_datasets()
36
+ target = ds[ds["publication_id"].notna()]["atlas_id"].iloc[0]
37
+ cite = aabatlas.get_citation(target)
38
+ for key in ("atlas_id", "citation", "doi"):
39
+ assert key in cite