Skip to main content

Python library to search Magic The Gathering cards information on scryfall.com

Project description

PyScryfall: A WIP/unofficial Scryfall API Wrapper

Python client for the Scryfall REST API: search Magic: The Gathering cards by name or Scryfall ID and work with typed card payloads (dataclass models). Fully compliant with Scryfall API guidelines including rate limiting and required headers.

Requirements: Python 3.12+, requests. Network access is required for live API calls.

Installation

Using uv (recommended for this repo):

uv sync

That creates or updates .venv, installs runtime dependencies, and includes the dev dependency group (pytest) by default via default-groups in pyproject.toml.

To install only production dependencies in another workflow, use your tool’s equivalent of installing the pyscryfall project with its [project] dependencies.

Package layout

The library lives under src/pyscryfall/ on disk. After uv sync or pip install, you import it as pyscryfall. Modules split HTTP entry points, typed JSON models, errors, and internal parsing helpers.

Repository layout (high level):

src/pyscryfall/    # installable package (api, schemas, exceptions, helpers, __init__)
tests/
pyproject.toml
flowchart LR
    subgraph public [Public surface]
        init["pyscryfall/__init__.py"]
    end
    subgraph impl [Implementation]
        api["api.py"]
        schemas["schemas.py"]
        exceptions["exceptions.py"]
        helpers["helpers.py"]
    end
    init --> api
    init --> schemas
    init --> exceptions
    api --> schemas
    api --> exceptions
    schemas --> helpers
Module Role
pyscryfall.api GET requests to Scryfall, JSON parsing, validation of object field, construction of ScryfallCard / ScryfallCardList.
pyscryfall.schemas Dataclasses mirroring Scryfall card and list JSON; from_dict / to_dict (and list loads / dumps).
pyscryfall.exceptions ScryfallApiError for HTTP failures and API error payloads; ScryfallErrorBody for structured error fields.
pyscryfall.helpers Internal helpers (_optional_model, _list_of, …) used by schemas; not part of the public __all__.

The package root re-exports the search functions, core card types, and exceptions (see src/pyscryfall/__init__.py __all__).

API and data flow

High-level flow from your code to typed objects:

sequenceDiagram
    participant App as YourCode
    participant API as pyscryfall.api
    participant HTTP as ScryfallHTTPServer
    participant Sch as schemas

    App->>API: search_cards_by_name or search_card_by_id
    API->>API: enforce rate limiting (10 req/sec)
    API->>HTTP: GET cards/search or GET cards/id (with User-Agent)
    HTTP-->>API: JSON body
    API->>API: parse JSON, check object type
    alt success list
        API->>Sch: ScryfallCardList.from_dict
        Sch-->>App: ScryfallCardList
    else success card
        API->>Sch: ScryfallCard.from_dict
        Sch-->>App: ScryfallCard
    else HTTP error or object error
        API-->>App: ScryfallApiError
    end

Core Search Functions

  • search_cards_by_name(name, …)GET /cards/search with a name:"…" query (quotes escaped). Returns ScryfallCardList (first page, up to 175 cards).
  • search_card_by_id(card_id, …)GET /cards/:id. Returns a single ScryfallCard.

Named Search Functions

  • search_card_by_name_exact(name, …)GET /cards/named?exact= for exact name matching. Returns a single ScryfallCard.
  • search_card_by_name_fuzzy(name, …)GET /cards/named?fuzzy= for fuzzy matching (handles typos). Returns a single ScryfallCard.

Pagination

  • search_cards_by_name_all(name, …) → Generator that automatically follows next_page URLs to yield all matching cards across all pages.

Other Endpoints

All functions accept optional session (requests.Session) and timeout. Base URL defaults to https://api.scryfall.com; override with environment variable SCRYFALL_BASE_URL (e.g. for tests or mocks).

Class and composition model

ScryfallCard is the main aggregate: many optional fields and nested dataclasses. ScryfallCardList wraps a page of cards.

classDiagram
    direction TB
    class ScryfallCardList {
        +str object
        +int total_cards
        +bool has_more
        +list data
        +str next_page
        +from_dict()
        +loads()
        +to_dict()
        +dumps()
    }
    class ScryfallCard {
        +str id
        +str name
        +from_dict()
        +to_dict()
    }
    class CardFace {
        +from_dict()
    }
    class ImageUris
    class Prices
    class PreviewInfo
    class RelatedUris
    class PurchaseUris
    class ScryfallRelatedCard

    ScryfallCardList "1" --> "*" ScryfallCard : data
    ScryfallCard "0..*" --> CardFace : card_faces
    ScryfallCard "0..1" --> ImageUris : image_uris
    ScryfallCard "0..1" --> Prices : prices
    ScryfallCard "0..1" --> PreviewInfo : preview
    ScryfallCard "0..1" --> RelatedUris : related_uris
    ScryfallCard "0..1" --> PurchaseUris : purchase_uris
    ScryfallCard "0..*" --> ScryfallRelatedCard : all_parts
    CardFace "0..1" --> ImageUris : image_uris

Errors from the library use ScryfallApiError: message, optional http_status, and optional body (ScryfallErrorBody with code, details, status, etc.) when Scryfall returns an error object.

Usage examples

Search by name (first page)

from pyscryfall import search_cards_by_name, ScryfallCardList

result: ScryfallCardList = search_cards_by_name("Lightning Bolt")
print(result.total_cards, result.has_more)
for card in result.data:
    print(card.name, card.set_name, card.collector_number)

Optional arguments match Scryfall’s search API (see docstrings): e.g. unique="prints", order="released".

Exact and fuzzy name search

from pyscryfall import search_card_by_name_exact, search_card_by_name_fuzzy

# Exact match (case-insensitive)
card = search_card_by_name_exact("Lightning Bolt")
print(card.name)

# Fuzzy match (handles typos and partial names)
card = search_card_by_name_fuzzy("Lightn Bolt")  # finds Lightning Bolt
print(card.name)

# Restrict to specific set
card = search_card_by_name_exact("Lightning Bolt", set_code="m21")
print(card.set_name)

Fetch all pages with pagination

from pyscryfall import search_cards_by_name_all

# Generator yields cards from all pages
for card in search_cards_by_name_all("Island", unique="prints"):
    print(card.set_name, card.collector_number)
    
# Or collect all at once
cards = list(search_cards_by_name_all("Island", unique="prints"))
print(f"Found {len(cards)} total printings")

Fetch a single card by Scryfall ID

from pyscryfall import search_card_by_id, ScryfallCard

card: ScryfallCard = search_card_by_id("de652420-eacf-4f9d-9f13-c6bc02b0fa72")
print(card.name, card.type_line, card.oracle_text)

Random card

from pyscryfall import get_random_card

# Get any random card
card = get_random_card()
print(card.name)

# Get random card matching a query
creature = get_random_card(q="t:creature")
print(creature.type_line)

# Random legendary from a specific set
legendary = get_random_card(q="t:legendary set:war")
print(legendary.name)

Autocomplete card names

from pyscryfall import autocomplete_card_name

suggestions = autocomplete_card_name("Lightning")
print(suggestions.data)  # ["Light", "Lightning Bolt", "Lightning Strike", ...]

# Include extra cards (tokens, etc.)
all_suggestions = autocomplete_card_name("Thopter", include_extras=True)

Card rulings

from pyscryfall import search_card_by_name_exact, get_card_rulings

card = search_card_by_name_exact("Humility")
rulings = get_card_rulings(card.id)

for ruling in rulings.data:
    print(f"{ruling.published_at}: {ruling.comment}")

Handle API and HTTP errors

from pyscryfall import search_card_by_id, ScryfallApiError

try:
    search_card_by_id("00000000-0000-0000-0000-000000000000")
except ScryfallApiError as exc:
    print(exc)
    print(exc.http_status)
    if exc.body:
        print(exc.body.code, exc.body.details)

Custom session or timeout

import requests
from pyscryfall import search_cards_by_name

session = requests.Session()
session.headers["User-Agent"] = "MyApp/1.0 (contact@example.com)"
cards = search_cards_by_name("Island", session=session, timeout=60.0)

Note: The library automatically sets a User-Agent header (pyscryfall/0.2.0), but you can override it by providing a custom session with your own User-Agent.

Serialize models

from pyscryfall import search_card_by_id

card = search_card_by_id("de652420-eacf-4f9d-9f13-c6bc02b0fa72")
payload = card.to_dict()
json_string = card.to_json()

# Deserialize
from pyscryfall import ScryfallCard
card = ScryfallCard.from_json_string(json_string)

For a stored JSON string of a list response, ScryfallCardList.from_json_string(s) and to_json() are available on the list type.

Rate limiting and API compliance

This library follows Scryfall's API guidelines:

Automatic rate limiting

The library automatically enforces Scryfall's 10 requests/second limit by adding a small delay between requests. This is transparent and requires no configuration.

Override the delay with the SCRYFALL_RATE_LIMIT_DELAY environment variable (in seconds):

export SCRYFALL_RATE_LIMIT_DELAY=0.15  # 150ms = ~6.7 req/sec

Rate limiting is shared across all API calls in the process.

User-Agent header

All requests automatically include a User-Agent: pyscryfall/0.2.0 header as required by Scryfall.

To use a custom User-Agent (e.g., for your application):

import requests
from pyscryfall import search_cards_by_name

session = requests.Session()
session.headers["User-Agent"] = "MyApp/1.0 (contact@example.com)"
cards = search_cards_by_name("Island", session=session)

Error handling

The library properly handles API error responses with structured error information:

from pyscryfall import search_card_by_name_exact, ScryfallApiError

try:
    card = search_card_by_name_exact("NonexistentCard12345")
except ScryfallApiError as e:
    print(f"HTTP Status: {e.http_status}")
    if e.body:
        print(f"Error Code: {e.body.code}")
        print(f"Details: {e.body.details}")

Running tests

Tests live under tests/ and call the real Scryfall API, so they need network access.

With uv (from the repository root):

uv sync
uv run pytest

Verbose output:

uv run pytest -v

uv sync installs this project in editable mode so import pyscryfall works. Pytest is configured in pyproject.toml with testpaths = ["tests"] and pythonpath = ["."].

References

AI Disclosure

Part of this project has been developed with the help of an AI Model.

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

pyscryfall-0.2.0.tar.gz (41.0 kB view details)

Uploaded Source

Built Distribution

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

pyscryfall-0.2.0-py3-none-any.whl (27.0 kB view details)

Uploaded Python 3

File details

Details for the file pyscryfall-0.2.0.tar.gz.

File metadata

  • Download URL: pyscryfall-0.2.0.tar.gz
  • Upload date:
  • Size: 41.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyscryfall-0.2.0.tar.gz
Algorithm Hash digest
SHA256 760cb3844a66347642f2517948d8d81acf81fe525368513f1c18c73ca2765bdb
MD5 eb732e97cc13093a5efb94929dc6b9fc
BLAKE2b-256 b0c658ba71924cf8df4d9deee7e36d785caebd426130c00ec00bc2e28ff30ef3

See more details on using hashes here.

File details

Details for the file pyscryfall-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyscryfall-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 27.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyscryfall-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9b03fe51749379068989159fb6caf86f5ce79c292d92b74a94a4f736abd546af
MD5 15089d8c49552fb0a8cf0a51ab88d23c
BLAKE2b-256 289e4ae672f0ef82a883103c195216c95149658d06f8d6f2b43013663cabd000

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