SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
8.8 KB · 171 lines python
Raw Blame History
1"""PyPI — `https://pypi.org/pypi/<pkg>/json` (public JSON document) for the AI Python ecosystem. Tier 2.23Packages = the `pypi` names of registry/repositories.yaml (identifiers `pypi` + `github_repo` → merges with the GitHub entity) plus a4curated list of standalone libraries (identifiers `pypi` only, entity type `library`).56Claims: `pypi_version`, `pypi_release_at` (earliest upload time of the latest release's files), `license`, `description` (summary),7`homepage`, `requires_python`, `metric.releases`, `repository_url` (when the project links GitHub). For packages that are NOT mapped to a8GitHub repository the connector also sets `latest_version` / `latest_release_at`; for mapped packages those two properties are owned by9the GitHub release feed so two same-tier sources never flip-flop the same property when a tag lands hours before the wheel.10"""11from __future__ import annotations1213import re14from datetime import UTC15from typing import Any1617from aiatlas.registry import org_by_github, org_ref18from aiatlas.sdk.connector import BaseConnector, Parsed, RunContext19from aiatlas.sdk.extract.dates import parse_datetime20from aiatlas.sdk.facts import EntityRef, Facts, Target21from aiatlas.sdk.fetch import FetchResult2223from .github import claim_license, repo_entity, repo_entries2425EXTRA_PACKAGES = [26    "torch", "transformers", "vllm", "mlx", "mlx-lm", "llama-cpp-python", "langchain", "langgraph", "llama-index", "sglang", "jax", "onnxruntime",27    "diffusers", "peft", "trl", "unsloth", "openai", "anthropic", "google-genai", "mistralai", "cohere", "litellm", "faiss-cpu", "chromadb",28    "qdrant-client", "pymilvus", "weaviate-client", "lancedb", "crewai", "pydantic-ai", "browser-use", "ragas", "langfuse", "ray", "bentoml",29    "triton", "flash-attn", "lm-eval", "sentence-transformers", "accelerate", "bitsandbytes", "autoawq", "gguf", "safetensors", "tokenizers",30    "datasets", "evaluate", "gradio", "streamlit", "fastapi", "huggingface-hub", "optimum", "xformers", "einops", "tiktoken", "instructor",31    "dspy", "outlines", "guidance", "semantic-kernel", "haystack-ai", "autogen-agentchat", "smolagents", "mcp", "openai-agents", "letta",32    "vector-quantize-pytorch", "timm", "torchvision", "torchaudio", "scikit-learn", "xgboost", "lightgbm", "keras", "tensorflow", "deepspeed",33    "megatron-core", "flax", "optax", "numpyro", "pyro-ppl", "spacy", "nltk", "whisperx", "faster-whisper", "ctranslate2", "onnx", "tensorrt",34    "nvidia-modelopt", "auto-gptq", "exllamav2", "llmcompressor", "sparseml", "lmdeploy", "text-generation", "openllm", "modal", "wandb", "mlflow",35    "comet-ml", "arize-phoenix", "opentelemetry-instrumentation-openai", "promptfoo", "deepeval", "inspect-ai", "trulens", "guardrails-ai",36]37GITHUB_URL = re.compile(r"https?://github\.com/([\w.-]+/[\w.-]+)")383940class PyPIConnector(BaseConnector):41    name = "pypi"42    label = "PyPI — versions and metadata of the AI Python ecosystem"43    description = "Public JSON documents of the packages in registry/repositories.yaml plus a curated list of standalone AI libraries."44    source_key = "pypi.org"45    version = "2"46    parser_version = "2"47    interval_seconds = 12 * 360048    min_interval_seconds = 6 * 360049    max_interval_seconds = 3 * 8640050    rate_per_min = 3051    tier = 252    priority = 253    expected_min_records = 6054    concurrency = 35556    def packages(self) -> dict[str, dict[str, Any] | None]:57        out: dict[str, dict[str, Any] | None] = {}58        for entry in repo_entries():59            if entry.get("pypi"):60                out[_norm(entry["pypi"])] = entry61        for pkg in self.config.get("packages") or EXTRA_PACKAGES:62            out.setdefault(_norm(pkg), None)63        return out6465    async def discover(self, ctx: RunContext) -> list[Target]:66        return [Target(url=f"https://pypi.org/pypi/{pkg}/json", doc_type="package", key=f"pkg:{pkg}", min_bytes=200,67                       meta={"pypi": pkg, "repo": (entry or {}).get("repo"), "content_type": "application/json"}, priority=2)68                for pkg, entry in self.packages().items()]6970    async def extract(self, ctx: RunContext, target: Target, res: FetchResult, parsed: Parsed) -> Facts:71        facts = Facts()72        data = parsed.json if parsed.kind == "json" else None73        if not isinstance(data, dict) or not isinstance(data.get("info"), dict):74            return facts75        info = data["info"]76        pkg = _norm(info.get("name") or target.meta.get("pypi") or "")77        if not pkg:78            return facts79        entry = next((e for e in repo_entries() if e.get("pypi") and _norm(e["pypi"]) == pkg), None)80        urls = {k.lower(): v for k, v in (info.get("project_urls") or {}).items() if isinstance(v, str)}81        gh = _github(urls, info.get("home_page"))82        if entry:83            ref = repo_entity(facts, entry)84        else:85            org = None86            if gh:87                known = org_by_github(gh.split("/")[0])88                org = org_ref(known["key"]) if known else None89            ids = {"pypi": pkg}90            if gh and _norm(gh.split("/")[1]) == pkg:      # `gguf` links to ggml-org/llama.cpp: a dependency, not the same project91                ids["github_repo"] = gh92            ref = facts.entity("library", info.get("name") or pkg, identifiers=ids, organization=org, aliases=[pkg])93            if org:94                facts.entities.append(org)95                facts.relate(org, "develops", ref)96            if gh:97                facts.claim(ref, "repository_url", f"https://github.com/{gh}")98        facts.claim(ref, "pypi", pkg)99        facts.claim(ref, "pypi_url", info.get("package_url") or f"https://pypi.org/project/{pkg}/")100        version = info.get("version")101        released = _release_time(data, version)102        facts.claim(ref, "pypi_version", version)103        facts.claim(ref, "pypi_release_at", released)104        if not entry:105            facts.claim(ref, "latest_version", version)106            facts.claim(ref, "latest_release_at", released)107        lic = _license(info)108        claim_license(facts, ref, lic, prop="pypi_license")109        if not entry:110            claim_license(facts, ref, lic)                # mapped repos: GitHub's SPDX id owns `license` (avoids same-tier flip-flops)111            facts.claim(ref, "kind", "library")112        summary = (info.get("summary") or "").strip()113        facts.claim(ref, "description", summary[:1000] or None)114        homepage = urls.get("homepage") or urls.get("home") or info.get("home_page")115        if not entry and isinstance(homepage, str) and homepage.startswith("http"):116            facts.claim(ref, "homepage", homepage)117        docs = urls.get("documentation") or urls.get("docs")118        facts.claim(ref, "docs_url", docs if isinstance(docs, str) and docs.startswith("http") else None)119        facts.claim(ref, "requires_python", info.get("requires_python") or None)120        facts.claim(ref, "author", (info.get("author") or "").strip() or None)121        facts.claim(ref, "metric.releases", len(data.get("releases") or {}))122        facts.claim(ref, "python_classifiers", [c for c in info.get("classifiers") or [] if c.startswith("Programming Language :: Python :: 3.")][:12])123        facts.document_entity = ref124        facts.document_title = f"{info.get('name') or pkg} {version or ''} on PyPI".strip()125        return facts126127128def _norm(name: str) -> str:129    return re.sub(r"[-_.]+", "-", name.strip()).lower()130131132def _github(urls: dict[str, str], home: Any) -> str | None:133    for key in ("source", "source code", "repository", "code", "github", "homepage", "home"):134        v = urls.get(key)135        if v:136            m = GITHUB_URL.search(v)137            if m:138                return m.group(1).removesuffix(".git")139    if isinstance(home, str):140        m = GITHUB_URL.search(home)141        if m:142            return m.group(1).removesuffix(".git")143    return None144145146def _license(info: dict[str, Any]) -> str | None:147    expr = info.get("license_expression")148    if isinstance(expr, str) and expr.strip():149        return expr.strip()150    lic = info.get("license")151    if isinstance(lic, str) and 0 < len(lic.strip()) <= 80:152        return lic.strip()153    for c in info.get("classifiers") or []:154        if c.startswith("License :: OSI Approved :: "):155            return c.rsplit(" :: ", 1)[1]156        if c.startswith("License :: ") and c != "License :: OSI Approved":157            return c.rsplit(" :: ", 1)[1]158    return None159160161def _release_time(data: dict[str, Any], version: str | None) -> str | None:162    files = (data.get("releases") or {}).get(version or "") or data.get("urls") or []163    times = [parse_datetime(f.get("upload_time_iso_8601") or f.get("upload_time")) for f in files if isinstance(f, dict)]164    times = [t for t in times if t]165    return min(times).astimezone(UTC).isoformat(timespec="seconds") if times else None166167168__all__ = ["EXTRA_PACKAGES", "PyPIConnector"]169_ = EntityRef  # re-exported type for tests170CONNECTORS = [PyPIConnector]171