Skip to main content

Minimal utilities to build scrapeable API models on top of Pydantic with simple caching and async HTTP.

Project description

pydantic-scrapeable-api-model

Minimal utilities to build scrapeable API models on top of Pydantic with simple on-disk caching and async HTTP.

Relies on pydantic-cacheable-model for JSON-based caching.

Install

pip install pydantic-scrapeable-api-model

Usage

Quickstart

from __future__ import annotations

import asyncio
from typing import Any

from pydantic_scrapeable_api_model import CacheKey, ScrapeableApiModel, ScrapeableField


class MyAPI(ScrapeableApiModel):
    BASE_URL = "https://api.example.com"
    list_endpoint = "/items"

    id: CacheKey[int]  # required, unique key (or create a custom `id` property)
    name: str
    # lazily-scraped field (filled by `scrape_detail` or a custom getter)
    description: ScrapeableField[str]

    @property
    def detail_endpoint(self) -> str | None:
        return f"/items/{self.id}"

    # Optional: fetch a field yourself instead of relying on detail_endpoint
    async def scrape_description(self) -> None:
        resp = await self.request(
            id=f"item-{self.id}-desc",
            url=self._build_url(f"/items/{self.id}/description"),
            headers={"Accept": "application/json"},
        )
        if resp:
            self.description = resp.json()["description"]


async def main() -> None:
    # Scrape list + detail for all models of MyAPI
    await MyAPI.scrape_all(check_api=True, use_cache=True)

    # Work with cached data later
    items = list(MyAPI.load_all_cached())
    first = items[0]
    print(first.model_dump())


asyncio.run(main())

Run All Subclasses

from pydantic_scrapeable_api_model import CacheKey, ScrapeableApiModel

# Define a base that sets the host
class Base(ScrapeableApiModel):
    BASE_URL = "https://api.example.com"


class Users(Base):
    list_endpoint = "/users"
    id: CacheKey[int]
    username: str


class Posts(Base):
    list_endpoint = "/posts"
    id: CacheKey[int]
    title: str


# Discover and run all children concurrently
asyncio.run(Base.run(use_cache=True, check_api=True))

Absolute Endpoints (no BASE_URL)

from pydantic_scrapeable_api_model import CacheKey, ScrapeableApiModel

class ExternalFeed(ScrapeableApiModel):
    BASE_URL = ""  # allowed when list_endpoint is absolute
    list_endpoint = "https://example.org/feed.json"
    id: CacheKey[int]
    title: str

# Works because the endpoint is absolute
asyncio.run(ExternalFeed.scrape_all(check_api=True, use_cache=True))

Cached Access Helpers

# Load everything from cache
cached_items = list(MyAPI.load_all_cached())

# Fetch one by key (fallback to API when allowed)
item = asyncio.run(MyAPI.get(cache_key="123", check_api=True))

Configure Logging

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s - %(message)s",
)

# Library logs under module name
logging.getLogger("pydantic_scrapeable_api_model").setLevel(logging.INFO)

Custom Response Mapping

Override response_to_models() to adapt to non-trivial API shapes:

from typing import Sequence
import httpx

class MyWrappedAPI(MyAPI):
    # Server wraps results like: {"data": {"items": [ ... ]}}
    @classmethod
    def response_to_models(cls, resp: httpx.Response) -> Sequence["MyWrappedAPI"]:
        payload = resp.json()
        items = payload.get("data", {}).get("items", [])
        return [cls(**row) for row in items]

API Notes

MyModel.scrape_list(check_api=True|False|"/override") -> list[MyModel]
MyModel.scrape_all(check_api=True, use_cache=True) -> None
MyModel.run(use_cache=True, check_api=True) -> None  # runs all subclasses
MyModel.get(cache_key=..., check_api=False) -> Optional[MyModel]
MyModel.load_all_cached() -> Iterable[MyModel]
instance.scrape_detail(use_cache=True) -> None
instance.model_dump() -> dict (unscraped fields omitted)
MyModel.response_to_models(resp) -> Sequence[MyModel]  # customize parsing

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pydantic_scrapeable_api_model-0.1.0.tar.gz (6.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file pydantic_scrapeable_api_model-0.1.0.tar.gz.

File metadata

File hashes

Hashes for pydantic_scrapeable_api_model-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e5ffac7e8c765f89bd6e1c8685c5af192f3f1f871e1e23b9e31a5c3376c92b7d
MD5 f8f8a97749a69ae4ecd1909622399491
BLAKE2b-256 dae922d906712bb18b02b3130487991c90b85e0869d4fed4868be7a3e1130495

See more details on using hashes here.

File details

Details for the file pydantic_scrapeable_api_model-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_scrapeable_api_model-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4db88617aa7fff5e1fbf214ef251d645bbcf92a58f634101bf1eeb80bb1bdc04
MD5 17ecb6561efca70db40f9f304631b12f
BLAKE2b-256 3d9150a20edfac2c4799a1889ece03ce1b916162ab589afad1b0c686f9f28204

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page