Skip to content

Database & Search API Reference

This module covers PGN archive and Scid binary database indexing, multi-criteria header queries, and filter bitsets.


Database

libscid.Database

Database()

Chess game database supporting fast header indexing and subset queries.

A Database manages an indexed collection of chess games, supporting high-speed header queries, tag extraction, game deserialisation, filter subset management via DatabaseFilters, and search execution via DatabaseSearch.

Direct instantiation of Database is disallowed; instances are opened via factory class methods such as open_pgn_read_only().

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "Match"]\n[White "Capablanca"]\n[Black "Lasker"]'
...     '\n\n1. e4 e5 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> database = libscid.Database.open_pgn_read_only(path)
>>> database.game_count
1
>>> database.get_tag(0, "White")
'Capablanca'
>>> game = database.get_game(0)
>>> game.mainline_move_count
2
>>> database.close()
>>> pathlib.Path(path).unlink()

Disallow direct database instantiation.

Raises:

Type Description
TypeError

Always raised if instantiated directly.

Source code in src/libscid/_database.py
def __init__(self):
    """Disallow direct database instantiation.

    Raises:
        TypeError: Always raised if instantiated directly.
    """
    raise TypeError("Database objects are returned by libscid APIs")

Attributes

type property

type: str

Database backend format identifier (e.g. 'PGN', 'Scid5', 'Memory').

Examples:

>>> import tempfile, pathlib, libscid
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write('[Event "E"]\n[White "W"]\n\n1. e4 1-0\n')
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.type
'PGN'
>>> db.close()
>>> pathlib.Path(path).unlink()

read_only property

read_only: bool

True if the database was opened in read-only mode.

Examples:

>>> import tempfile, pathlib, libscid
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write('[Event "E"]\n[White "W"]\n\n1. e4 1-0\n')
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.read_only
True
>>> db.close()
>>> pathlib.Path(path).unlink()

game_count property

game_count: int

Total number of games indexed in the database.

Examples:

>>> import tempfile, pathlib, libscid
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write('[Event "E"]\n[White "W"]\n\n1. e4 1-0\n')
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.game_count
1
>>> db.close()
>>> pathlib.Path(path).unlink()

filters property

filters: DatabaseFilters

Filter manager for accessing and creating game subset views.

Examples:

>>> import tempfile, pathlib, libscid
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write('[Event "E"]\n[White "W"]\n\n1. e4 1-0\n')
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.filters.all_games.game_count
1
>>> db.close()
>>> pathlib.Path(path).unlink()

search property

Query engine for executing header, board, and position searches.

Examples:

>>> import tempfile, pathlib, libscid
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(
...         '[Event "E"]\n[White "Capablanca"]\n\n1. e4 1-0\n'
...     )
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> criteria = libscid.HeaderCriteria(white="Capablanca")
>>> filter_view = db.search.headers(criteria)
>>> filter_view.game_count
1
>>> db.close()
>>> pathlib.Path(path).unlink()

Methods:

open_pgn_read_only classmethod

open_pgn_read_only(
    path: str | PathLike[str],
    progress_report_callback: ProgressReportCallback
    | None = None,
    should_cancel: ShouldCancelFn | None = None,
) -> Database

Open a Portable Game Notation (.pgn) archive in read-only mode.

Scans the PGN text archive, indexing game offsets and header metadata in memory for fast random access and searching.

Parameters:

Name Type Description Default
path str | PathLike[str]

Filesystem path to the PGN file.

required
progress_report_callback ProgressReportCallback | None

Optional callback receiving (done, total, message) progress updates during indexing.

None
should_cancel ShouldCancelFn | None

Optional predicate function returning True to request cooperative early cancellation.

None

Returns:

Type Description
Database

An opened read-only Database instance.

Raises:

Type Description
LibScidError

If the file cannot be opened, is malformed, or indexing is cancelled.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "Hastings"]\n[White "Capa"]\n[Black "Lasker"]'
...     '\n\n1. e4 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.game_count
1
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_database.py
@classmethod
def open_pgn_read_only(
    cls,
    path: str | os.PathLike[str],
    progress_report_callback: ProgressReportCallback | None = None,
    should_cancel: ShouldCancelFn | None = None,
) -> Database:
    """Open a Portable Game Notation (.pgn) archive in read-only mode.

    Scans the PGN text archive, indexing game offsets and header metadata
    in memory for fast random access and searching.

    Args:
        path: Filesystem path to the PGN file.
        progress_report_callback: Optional callback receiving `(done, total,
            message)` progress updates during indexing.
        should_cancel: Optional predicate function returning True to
            request cooperative early cancellation.

    Returns:
        An opened read-only [`Database`][libscid.Database] instance.

    Raises:
        LibScidError: If the file cannot be opened, is malformed, or
            indexing is cancelled.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> pgn = (
        ...     '[Event "Hastings"]\\n[White "Capa"]\\n[Black "Lasker"]'
        ...     '\\n\\n1. e4 1-0\\n'
        ... )
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(pgn)
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> db.game_count
        1
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    native = load_library()
    return cls._from_handle(
        native,
        native.open_pgn_database_read_only(
            path,
            progress_report_callback=progress_report_callback,
            should_cancel=should_cancel,
        ),
    )

get_tag

get_tag(index: int, name: str | bytes) -> str

Retrieve a header tag value for a game directly from the index.

Retrieves metadata (e.g. player names, event, date, ECO, result) without parsing the complete game movetext.

Parameters:

Name Type Description Default
index int

0-based database game index.

required
name str | bytes

PGN tag header name (e.g. "White", "Date", "ECO").

required

Returns:

Type Description
str

The tag value string, or an empty string if the tag is absent.

Raises:

Type Description
LibScidError

If index is out of bounds or the database is closed.

Examples:

>>> import tempfile, pathlib, libscid
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(
...         '[Event "Match"]\n[White "Tal"]\n[Black "Botvinnik"]'
...         '\n\n1. e4 1-0\n'
...     )
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.get_tag(0, "White")
'Tal'
>>> db.get_tag(0, "Black")
'Botvinnik'
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_database.py
def get_tag(self, index: int, name: str | bytes) -> str:
    """Retrieve a header tag value for a game directly from the index.

    Retrieves metadata (e.g. player names, event, date, ECO, result)
    without parsing the complete game movetext.

    Args:
        index: 0-based database game index.
        name: PGN tag header name (e.g. "White", "Date", "ECO").

    Returns:
        The tag value string, or an empty string if the tag is absent.

    Raises:
        LibScidError: If `index` is out of bounds or the database is closed.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(
        ...         '[Event "Match"]\\n[White "Tal"]\\n[Black "Botvinnik"]'
        ...         '\\n\\n1. e4 1-0\\n'
        ...     )
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> db.get_tag(0, "White")
        'Tal'
        >>> db.get_tag(0, "Black")
        'Botvinnik'
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    return self._native.database_game_tag(self._handle, index, name)

get_game

get_game(index: int) -> Game

Load and deserialise the full chess game at the specified index.

Parameters:

Name Type Description Default
index int

0-based database game index.

required

Returns:

Type Description
Game

A newly allocated Game instance containing all header tags, mainline moves, variation branches, and comments.

Raises:

Type Description
LibScidError

If index is out of bounds or game deserialisation fails.

Examples:

>>> import tempfile, pathlib, libscid
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(
...         '[Event "Match"]\n[White "Tal"]\n[Black "Botvinnik"]'
...         '\n\n1. e4 e5 1-0\n'
...     )
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> game = db.get_game(0)
>>> game.get_tag("White")
'Tal'
>>> game.mainline_move_count
2
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_database.py
def get_game(self, index: int) -> Game:
    """Load and deserialise the full chess game at the specified index.

    Args:
        index: 0-based database game index.

    Returns:
        A newly allocated [`Game`][libscid.Game] instance containing all
            header tags, mainline moves, variation branches, and comments.

    Raises:
        LibScidError: If `index` is out of bounds or game deserialisation fails.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(
        ...         '[Event "Match"]\\n[White "Tal"]\\n[Black "Botvinnik"]'
        ...         '\\n\\n1. e4 e5 1-0\\n'
        ...     )
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> game = db.get_game(0)
        >>> game.get_tag("White")
        'Tal'
        >>> game.mainline_move_count
        2
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    return Game._from_handle(
        self._native, self._native.database_game(self._handle, index)
    )

close

close() -> None

Close database storage files and release resources.

Examples:

>>> import tempfile, pathlib, libscid
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write('[Event "E"]\n[White "W"]\n\n1. e4 1-0\n')
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_database.py
def close(self) -> None:
    """Close database storage files and release resources.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write('[Event "E"]\\n[White "W"]\\n\\n1. e4 1-0\\n')
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    handle = getattr(self, "_handle", None)
    if handle:
        self._native.close_database(handle)

DatabaseFilters

libscid.DatabaseFilters

DatabaseFilters()

Manager for chess database selection filters and subset views.

Provides access to universal and primary predefined filters as well as allocation of custom user-defined filters for query execution, sorting, and game subset inspection.

Direct instantiation of DatabaseFilters is disallowed; instances are accessed via the Database.filters property.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = '[Event "E1"]\n\n1. e4 1-0\n\n[Event "E2"]\n\n1. d4 1-0\n'
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> database = libscid.Database.open_pgn_read_only(path)
>>> database.filters.all_games.game_count
2
>>> database.filters.primary.game_count
2
>>> custom_filter = database.filters.create()
>>> custom_filter.game_count
2
>>> custom_filter.delete()
>>> database.close()
>>> pathlib.Path(path).unlink()

Disallow direct filter manager instantiation.

Raises:

Type Description
TypeError

Always raised if instantiated directly.

Source code in src/libscid/_database_filters.py
def __init__(self):
    """Disallow direct filter manager instantiation.

    Raises:
        TypeError: Always raised if instantiated directly.
    """
    raise TypeError("DatabaseFilters objects are returned by libscid APIs")

Attributes

all_games property

all_games: Filter

Universal built-in filter matching all games in the database.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = '[Event "E1"]\n\n1. e4 1-0\n\n[Event "E2"]\n\n1. d4 1-0\n'
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.filters.all_games.game_count
2
>>> db.close()
>>> pathlib.Path(path).unlink()

primary property

primary: Filter

Primary working filter used for interactive search results.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = '[Event "E1"]\n\n1. e4 1-0\n\n[Event "E2"]\n\n1. d4 1-0\n'
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.filters.primary.game_count
2
>>> db.close()
>>> pathlib.Path(path).unlink()

Methods:

create

create() -> Filter

Allocate and register a new, empty user filter in the database.

Returns:

Type Description
Filter

A newly allocated user Filter.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = '[Event "E1"]\n\n1. e4 1-0\n'
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> custom = db.filters.create()
>>> custom.game_count
1
>>> custom.delete()
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_database_filters.py
def create(self) -> Filter:
    """Allocate and register a new, empty user filter in the database.

    Returns:
        A newly allocated user [`Filter`][libscid.Filter].

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> pgn = '[Event "E1"]\\n\\n1. e4 1-0\\n'
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(pgn)
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> custom = db.filters.create()
        >>> custom.game_count
        1
        >>> custom.delete()
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    return Filter._from_id(
        self._native,
        self._database,
        self._native.database_filter_create(self._database._handle),
        owned=True,
    )

DatabaseSearch

libscid.DatabaseSearch

DatabaseSearch()

Chess database query and search engine.

Executes fast header queries, exact position lookups via transposition hashing, and flexible board pattern searches against database filter subsets.

Direct instantiation of DatabaseSearch is disallowed; instances are accessed via the Database.search property.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "E"]\n[White "Fischer"]\n[Black "Spassky"]\n'
...     '[Result "1-0"]\n\n1. e4 e5 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> database = libscid.Database.open_pgn_read_only(path)
>>> criteria = libscid.HeaderCriteria(white="Fischer", result="1-0")
>>> matched_filter = database.search.headers(criteria)
>>> matched_filter.game_count
1
>>> database.close()
>>> pathlib.Path(path).unlink()

Disallow direct search engine instantiation.

Raises:

Type Description
TypeError

Always raised if instantiated directly.

Source code in src/libscid/_database_search.py
def __init__(self):
    """Disallow direct search engine instantiation.

    Raises:
        TypeError: Always raised if instantiated directly.
    """
    raise TypeError("DatabaseSearch objects are returned by libscid APIs")

Methods:

headers

headers(
    criteria: HeaderCriteria,
    *,
    source: Filter | None = None,
    destination: Filter | None = None,
    progress_report_callback: ProgressReportCallback
    | None = None,
    should_cancel_fn: ShouldCancelFn | None = None,
) -> Filter

Execute a multi-criteria header query across a database filter subset.

Scans game headers matching the criteria from the source filter, writing matching game indices into the destination filter.

Parameters:

Name Type Description Default
criteria HeaderCriteria

HeaderCriteria specifying header filters, rating ranges, results, and structural flags.

required
source Filter | None

Source Filter defining the search universe. If None, searches all games in the database.

None
destination Filter | None

Destination Filter to receive matching games. If None, a new filter is created.

None
progress_report_callback ProgressReportCallback | None

Optional progress callback receiving (done, total, message).

None
should_cancel_fn ShouldCancelFn | None

Optional predicate returning True to request cooperative cancellation.

None

Returns:

Type Description
Filter

The destination Filter containing matching game indices.

Raises:

Type Description
TypeError

If criteria is not a HeaderCriteria or filter parameters are invalid.

ValueError

If destination is the all_games filter or belongs to a different database.

LibScidError

If search execution fails or is cancelled.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "E1"]\n[White "Fischer"]\n[Result "1-0"]\n\n'
...     '1. e4 1-0\n\n'
...     '[Event "E2"]\n[White "Spassky"]\n[Result "0-1"]\n\n'
...     '1. d4 0-1\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> criteria = libscid.HeaderCriteria(white="Fischer")
>>> result_filter = db.search.headers(criteria)
>>> result_filter.game_count
1
>>> result_filter.get_game_indices()
(0,)
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_database_search.py
def headers(
    self,
    criteria: HeaderCriteria,
    *,
    source: Filter | None = None,
    destination: Filter | None = None,
    progress_report_callback: ProgressReportCallback | None = None,
    should_cancel_fn: ShouldCancelFn | None = None,
) -> Filter:
    """Execute a multi-criteria header query across a database filter subset.

    Scans game headers matching the criteria from the `source` filter,
    writing matching game indices into the `destination` filter.

    Args:
        criteria: [`HeaderCriteria`][libscid.HeaderCriteria] specifying
            header filters, rating ranges, results, and structural flags.
        source: Source [`Filter`][libscid.Filter] defining the search
            universe. If None, searches all games in the database.
        destination: Destination [`Filter`][libscid.Filter] to receive
            matching games. If None, a new filter is created.
        progress_report_callback: Optional progress callback receiving
            `(done, total, message)`.
        should_cancel_fn: Optional predicate returning True to request
            cooperative cancellation.

    Returns:
        The `destination` [`Filter`][libscid.Filter] containing matching
            game indices.

    Raises:
        TypeError: If `criteria` is not a `HeaderCriteria` or filter
            parameters are invalid.
        ValueError: If `destination` is the `all_games` filter or belongs to
            a different database.
        LibScidError: If search execution fails or is cancelled.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> pgn = (
        ...     '[Event "E1"]\\n[White "Fischer"]\\n[Result "1-0"]\\n\\n'
        ...     '1. e4 1-0\\n\\n'
        ...     '[Event "E2"]\\n[White "Spassky"]\\n[Result "0-1"]\\n\\n'
        ...     '1. d4 0-1\\n'
        ... )
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(pgn)
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> criteria = libscid.HeaderCriteria(white="Fischer")
        >>> result_filter = db.search.headers(criteria)
        >>> result_filter.game_count
        1
        >>> result_filter.get_game_indices()
        (0,)
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    if not isinstance(criteria, HeaderCriteria):
        raise TypeError("criteria must be a HeaderCriteria")

    source_filter = (
        self._database.filters.all_games
        if source is None
        else self._validate_filter("source", source)
    )
    destination_filter = (
        self._database.filters.create()
        if destination is None
        else self._validate_filter("destination", destination)
    )

    if destination_filter._id == SCID_FILTER_ALL_GAMES:
        raise ValueError("destination cannot be the all_games filter")

    native_criteria = criteria._create_native(self._native)
    try:
        self._native.database_search_headers(
            self._database._handle,
            source_filter._available_id(),
            destination_filter._available_id(),
            native_criteria,
            progress_report_callback=progress_report_callback,
            should_cancel_fn=should_cancel_fn,
        )
    finally:
        self._native.search_header_criteria_free(native_criteria)
    return destination_filter

position

position(
    position: Position,
    *,
    source: Filter | None = None,
    destination: Filter | None = None,
    progress_report_callback: ProgressReportCallback
    | None = None,
    should_cancel_fn: ShouldCancelFn | None = None,
) -> Filter

Search for games reaching an exact board position snapshot.

Performs fast transposition hash lookup across games in the source filter, writing matches into destination.

Parameters:

Name Type Description Default
position Position

Target Position to find.

required
source Filter | None

Source Filter defining the search subset. If None, searches all games in the database.

None
destination Filter | None

Destination Filter receiving matching games. If None, a new filter is created.

None
progress_report_callback ProgressReportCallback | None

Optional progress callback.

None
should_cancel_fn ShouldCancelFn | None

Optional cooperative cancellation predicate.

None

Returns:

Type Description
Filter

The destination Filter containing matching game indices.

Raises:

Type Description
TypeError

If position is not a Position.

ValueError

If destination is the all_games filter or belongs to a different database.

LibScidError

If search execution fails or is cancelled.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "E1"]\n\n1. e4 e5 1-0\n\n'
...     '[Event "E2"]\n\n1. d4 d5 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
>>> pos = libscid.Position.from_fen(fen)
>>> matched_filter = db.search.position(pos)
>>> matched_filter.game_count
1
>>> matched_filter.get_game_indices()
(0,)
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_database_search.py
def position(
    self,
    position: Position,
    *,
    source: Filter | None = None,
    destination: Filter | None = None,
    progress_report_callback: ProgressReportCallback | None = None,
    should_cancel_fn: ShouldCancelFn | None = None,
) -> Filter:
    """Search for games reaching an exact board position snapshot.

    Performs fast transposition hash lookup across games in the `source`
    filter, writing matches into `destination`.

    Args:
        position: Target [`Position`][libscid.Position] to find.
        source: Source [`Filter`][libscid.Filter] defining the search subset.
            If None, searches all games in the database.
        destination: Destination [`Filter`][libscid.Filter] receiving
            matching games. If None, a new filter is created.
        progress_report_callback: Optional progress callback.
        should_cancel_fn: Optional cooperative cancellation predicate.

    Returns:
        The `destination` [`Filter`][libscid.Filter] containing matching
            game indices.

    Raises:
        TypeError: If `position` is not a `Position`.
        ValueError: If `destination` is the `all_games` filter or belongs to
            a different database.
        LibScidError: If search execution fails or is cancelled.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> pgn = (
        ...     '[Event "E1"]\\n\\n1. e4 e5 1-0\\n\\n'
        ...     '[Event "E2"]\\n\\n1. d4 d5 1-0\\n'
        ... )
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(pgn)
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
        >>> pos = libscid.Position.from_fen(fen)
        >>> matched_filter = db.search.position(pos)
        >>> matched_filter.game_count
        1
        >>> matched_filter.get_game_indices()
        (0,)
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    if not isinstance(position, Position):
        raise TypeError("position must be a Position")

    source_filter = (
        self._database.filters.all_games
        if source is None
        else self._validate_filter("source", source)
    )
    destination_filter = (
        self._database.filters.create()
        if destination is None
        else self._validate_filter("destination", destination)
    )

    if destination_filter._id == SCID_FILTER_ALL_GAMES:
        raise ValueError("destination cannot be the all_games filter")

    self._native.database_search_position(
        self._database._handle,
        source_filter._available_id(),
        destination_filter._available_id(),
        position._handle,
        progress_report_callback=progress_report_callback,
        should_cancel_fn=should_cancel_fn,
    )
    return destination_filter

board

board(
    position: Position,
    *,
    match: BoardSearchMatch = BOARD_MATCH_EXACT,
    source: Filter | None = None,
    destination: Filter | None = None,
    include_variations: bool = False,
    include_flipped: bool = False,
    progress_report_callback: ProgressReportCallback
    | None = None,
    should_cancel_fn: ShouldCancelFn | None = None,
) -> Filter

Search for games matching a board configuration or material pattern.

Parameters:

Name Type Description Default
position Position

Target Position layout to match.

required
match BoardSearchMatch

Matching mode algorithm: "exact" (identical piece squares), "pawns" (identical pawn structure and piece count balance), or "files" (identical piece counts per file). Defaults to "exact".

BOARD_MATCH_EXACT
source Filter | None

Source Filter defining the search subset. If None, searches all games in the database.

None
destination Filter | None

Destination Filter receiving matches. If None, a new filter is created.

None
include_variations bool

Whether to search alternative variation branches in addition to the mainline. Defaults to False.

False
include_flipped bool

Whether to also match colour-flipped board positions (White and Black swapped). Defaults to False.

False
progress_report_callback ProgressReportCallback | None

Optional progress callback.

None
should_cancel_fn ShouldCancelFn | None

Optional cooperative cancellation predicate.

None

Returns:

Type Description
Filter

The destination Filter containing matching game indices.

Raises:

Type Description
TypeError

If position is not a Position.

ValueError

If match is invalid or destination is the all_games filter.

LibScidError

If search execution fails or is cancelled.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "E1"]\n\n1. e4 e5 1-0\n\n'
...     '[Event "E2"]\n\n1. d4 d5 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
>>> pos = libscid.Position.from_fen(fen)
>>> matched_filter = db.search.board(pos, match="exact")
>>> matched_filter.game_count
1
>>> matched_filter.get_game_indices()
(0,)
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_database_search.py
def board(
    self,
    position: Position,
    *,
    match: BoardSearchMatch = BOARD_MATCH_EXACT,
    source: Filter | None = None,
    destination: Filter | None = None,
    include_variations: bool = False,
    include_flipped: bool = False,
    progress_report_callback: ProgressReportCallback | None = None,
    should_cancel_fn: ShouldCancelFn | None = None,
) -> Filter:
    """Search for games matching a board configuration or material pattern.

    Args:
        position: Target [`Position`][libscid.Position] layout to match.
        match: Matching mode algorithm: "exact" (identical piece squares),
            "pawns" (identical pawn structure and piece count balance), or
            "files" (identical piece counts per file). Defaults to "exact".
        source: Source [`Filter`][libscid.Filter] defining the search subset.
            If None, searches all games in the database.
        destination: Destination [`Filter`][libscid.Filter] receiving
            matches. If None, a new filter is created.
        include_variations: Whether to search alternative variation branches
            in addition to the mainline. Defaults to False.
        include_flipped: Whether to also match colour-flipped board
            positions (White and Black swapped). Defaults to False.
        progress_report_callback: Optional progress callback.
        should_cancel_fn: Optional cooperative cancellation predicate.

    Returns:
        The `destination` [`Filter`][libscid.Filter] containing matching
            game indices.

    Raises:
        TypeError: If `position` is not a `Position`.
        ValueError: If `match` is invalid or `destination` is the `all_games`
            filter.
        LibScidError: If search execution fails or is cancelled.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> pgn = (
        ...     '[Event "E1"]\\n\\n1. e4 e5 1-0\\n\\n'
        ...     '[Event "E2"]\\n\\n1. d4 d5 1-0\\n'
        ... )
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(pgn)
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
        >>> pos = libscid.Position.from_fen(fen)
        >>> matched_filter = db.search.board(pos, match="exact")
        >>> matched_filter.game_count
        1
        >>> matched_filter.get_game_indices()
        (0,)
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    if not isinstance(position, Position):
        raise TypeError("position must be a Position")
    native_match = _native_board_match(match)
    include_variations = _boolean("include_variations", include_variations) != 0
    include_flipped = _boolean("include_flipped", include_flipped) != 0

    source_filter = (
        self._database.filters.all_games
        if source is None
        else self._validate_filter("source", source)
    )
    destination_filter = (
        self._database.filters.create()
        if destination is None
        else self._validate_filter("destination", destination)
    )

    if destination_filter._id == SCID_FILTER_ALL_GAMES:
        raise ValueError("destination cannot be the all_games filter")

    criteria = self._native.search_board_criteria_create()
    try:
        self._native._check(
            "scid_search_board_criteria_position_set",
            self._native._lib.scid_search_board_criteria_position_set(
                criteria, position._handle
            ),
        )
        self._native._check(
            "scid_search_board_criteria_match_set",
            self._native._lib.scid_search_board_criteria_match_set(
                criteria, native_match
            ),
        )
        self._native._check(
            "scid_search_board_criteria_include_variations_set",
            self._native._lib.scid_search_board_criteria_include_variations_set(
                criteria, int(include_variations)
            ),
        )
        self._native._check(
            "scid_search_board_criteria_include_flipped_set",
            self._native._lib.scid_search_board_criteria_include_flipped_set(
                criteria, int(include_flipped)
            ),
        )
        self._native.database_search_board(
            self._database._handle,
            source_filter._available_id(),
            destination_filter._available_id(),
            criteria,
            progress_report_callback=progress_report_callback,
            should_cancel_fn=should_cancel_fn,
        )
    finally:
        self._native.search_board_criteria_free(criteria)
    return destination_filter

HeaderCriteria

libscid.HeaderCriteria dataclass

HeaderCriteria(
    player: str | None = None,
    white: str | None = None,
    black: str | None = None,
    event: str | None = None,
    site: str | None = None,
    site_country: str | None = None,
    round: str | None = None,
    date_min: str | None = None,
    date_max: str | None = None,
    event_date_min: str | None = None,
    event_date_max: str | None = None,
    eco_min: str | None = None,
    eco_max: str | None = None,
    result: HeaderResult = None,
    game_number_min: int | None = None,
    game_number_max: int | None = None,
    halfmove_count_min: int | None = None,
    halfmove_count_max: int | None = None,
    white_elo_min: int | None = None,
    white_elo_max: int | None = None,
    black_elo_min: int | None = None,
    black_elo_max: int | None = None,
    elo_difference_min: int | None = None,
    elo_difference_max: int | None = None,
    has_variations: bool = False,
    has_comments: bool = False,
    has_nags: bool = False,
)

Multi-criteria search parameters for chess game headers.

Encapsulates text pattern filters, date/ECO ranges, rating limits, game lengths, tournament results, and structural movetext flags.

Attributes:

Name Type Description
player str | None

Substring match on either White or Black player name.

white str | None

Substring match on White player name.

black str | None

Substring match on Black player name.

event str | None

Substring match on event/tournament name.

site str | None

Substring match on venue/site name.

site_country str | None

Substring match on site country name or code.

round str | None

Substring match on round identifier.

date_min str | None

Minimum game date string (e.g. "1921.01.01").

date_max str | None

Maximum game date string (e.g. "1927.12.31").

event_date_min str | None

Minimum event date string.

event_date_max str | None

Maximum event date string.

eco_min str | None

Minimum ECO code classification (e.g. "B20").

eco_max str | None

Maximum ECO code classification (e.g. "B99").

result HeaderResult

Desired game outcome (e.g. "1-0", "0-1", "1/2-1/2", "*") or an iterable of acceptable outcomes.

game_number_min int | None

Minimum 1-based game number in the database.

game_number_max int | None

Maximum 1-based game number in the database.

halfmove_count_min int | None

Minimum game length in halfmoves (ply).

halfmove_count_max int | None

Maximum game length in halfmoves (ply).

white_elo_min int | None

Minimum Elo rating for White.

white_elo_max int | None

Maximum Elo rating for White.

black_elo_min int | None

Minimum Elo rating for Black.

black_elo_max int | None

Maximum Elo rating for Black.

elo_difference_min int | None

Minimum Elo difference (white_elo - black_elo).

elo_difference_max int | None

Maximum Elo difference (white_elo - black_elo).

has_variations bool

If True, matches only games with alternative variations.

has_comments bool

If True, matches only games with text commentary.

has_nags bool

If True, matches only games with Numeric Annotation Glyphs.

Examples:

>>> import libscid
>>> criteria = libscid.HeaderCriteria(
...     white="Kasparov",
...     result="1-0",
...     eco_min="B80",
...     eco_max="B89",
...     has_comments=True,
... )
>>> criteria.white
'Kasparov'
>>> criteria.result
'1-0'

Filter

libscid.Filter

Filter()

Subset view of games within a chess database.

A Filter represents a filtered or selected subset of games within a Database. Filters provide high-performance pagination, multi-criteria sorting, and bidirectional mapping between 0-based database game indices and sorted display row positions.

Filters are obtained via DatabaseFilters.all_games, DatabaseFilters.primary, DatabaseFilters.create(), or as search result destinations from DatabaseSearch.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "E1"]\n[White "W1"]\n\n1. e4 1-0\n\n'
...     '[Event "E2"]\n[White "W2"]\n\n1. d4 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> database = libscid.Database.open_pgn_read_only(path)
>>> all_games = database.filters.all_games
>>> all_games.game_count
2
>>> all_games.get_game_indices(start_row=0, row_count=2)
(0, 1)
>>> database.close()
>>> pathlib.Path(path).unlink()

Disallow direct filter instantiation.

Raises:

Type Description
TypeError

Always raised if instantiated directly.

Source code in src/libscid/_filter.py
def __init__(self):
    """Disallow direct filter instantiation.

    Raises:
        TypeError: Always raised if instantiated directly.
    """
    raise TypeError("Filter objects are returned by libscid APIs")

Attributes

game_count property

game_count: int

Total number of games currently matched by this filter.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "E1"]\n\n1. e4 1-0\n\n'
...     '[Event "E2"]\n\n1. d4 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.filters.all_games.game_count
2
>>> db.close()
>>> pathlib.Path(path).unlink()

Methods:

get_game_indices

get_game_indices(
    sort_criteria: str | bytes = "N+",
    start_row: int = 0,
    row_count: int | None = None,
) -> tuple[int, ...]

Retrieve database game indices in sorted display row order.

Parameters:

Name Type Description Default
sort_criteria str | bytes

Sorting specification string (e.g. "N+" for game number, "D-" for descending date, "W+" for White player, "B+" for Black player, "E+" for ECO code). Defaults to "N+".

'N+'
start_row int

0-based starting row offset in the sorted view. Defaults to 0.

0
row_count int | None

Maximum number of game indices to retrieve. If None, retrieves all remaining games from start_row.

None

Returns:

Type Description
tuple[int, ...]

A tuple of 0-based database game indices in sorted order.

Raises:

Type Description
ValueError

If start_row or row_count is negative, or if the filter has been deleted.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "E1"]\n\n1. e4 1-0\n\n'
...     '[Event "E2"]\n\n1. d4 1-0\n\n'
...     '[Event "E3"]\n\n1. c4 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> filter_view = db.filters.all_games
>>> filter_view.get_game_indices("N+", start_row=1, row_count=2)
(1, 2)
>>> filter_view.get_game_indices()
(0, 1, 2)
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_filter.py
def get_game_indices(
    self,
    sort_criteria: str | bytes = "N+",
    start_row: int = 0,
    row_count: int | None = None,
) -> tuple[int, ...]:
    """Retrieve database game indices in sorted display row order.

    Args:
        sort_criteria: Sorting specification string (e.g. "N+" for game
            number, "D-" for descending date, "W+" for White player, "B+"
            for Black player, "E+" for ECO code). Defaults to "N+".
        start_row: 0-based starting row offset in the sorted view. Defaults
            to 0.
        row_count: Maximum number of game indices to retrieve. If None,
            retrieves all remaining games from `start_row`.

    Returns:
        A tuple of 0-based database game indices in sorted order.

    Raises:
        ValueError: If `start_row` or `row_count` is negative, or if the
            filter has been deleted.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> pgn = (
        ...     '[Event "E1"]\\n\\n1. e4 1-0\\n\\n'
        ...     '[Event "E2"]\\n\\n1. d4 1-0\\n\\n'
        ...     '[Event "E3"]\\n\\n1. c4 1-0\\n'
        ... )
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(pgn)
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> filter_view = db.filters.all_games
        >>> filter_view.get_game_indices("N+", start_row=1, row_count=2)
        (1, 2)
        >>> filter_view.get_game_indices()
        (0, 1, 2)
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    self._check_non_negative("start_row", start_row)
    if row_count is None:
        row_count = max(self.game_count - start_row, 0)
    self._check_non_negative("row_count", row_count)
    return self._native.database_filter_game_indices(
        self._database._handle,
        self._available_id(),
        sort_criteria,
        start_row,
        row_count,
    )

get_game_index_at_row

get_game_index_at_row(
    row: int, sort_criteria: str | bytes = "N+"
) -> int

Retrieve the database game index for a specific sorted display row.

Parameters:

Name Type Description Default
row int

0-based display row index.

required
sort_criteria str | bytes

Sorting specification string. Defaults to "N+".

'N+'

Returns:

Type Description
int

The 0-based database game index at the specified row.

Raises:

Type Description
ValueError

If row is negative or out of bounds, or if the filter has been deleted.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "E1"]\n\n1. e4 1-0\n\n'
...     '[Event "E2"]\n\n1. d4 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.filters.all_games.get_game_index_at_row(1, "N+")
1
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_filter.py
def get_game_index_at_row(self, row: int, sort_criteria: str | bytes = "N+") -> int:
    """Retrieve the database game index for a specific sorted display row.

    Args:
        row: 0-based display row index.
        sort_criteria: Sorting specification string. Defaults to "N+".

    Returns:
        The 0-based database game index at the specified row.

    Raises:
        ValueError: If `row` is negative or out of bounds, or if the filter
            has been deleted.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> pgn = (
        ...     '[Event "E1"]\\n\\n1. e4 1-0\\n\\n'
        ...     '[Event "E2"]\\n\\n1. d4 1-0\\n'
        ... )
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(pgn)
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> db.filters.all_games.get_game_index_at_row(1, "N+")
        1
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    self._check_non_negative("row", row)
    return self._native.database_filter_game_index_at_row(
        self._database._handle, self._available_id(), sort_criteria, row
    )

get_game_row_for_index

get_game_row_for_index(
    game_index: int, sort_criteria: str | bytes = "N+"
) -> int

Find the sorted display row index for a specific database game index.

Parameters:

Name Type Description Default
game_index int

0-based database game index.

required
sort_criteria str | bytes

Sorting specification string. Defaults to "N+".

'N+'

Returns:

Type Description
int

The 0-based display row position of the game in the sorted filter view.

Raises:

Type Description
ValueError

If game_index is negative or not present in the filter, or if the filter has been deleted.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = (
...     '[Event "E1"]\n\n1. e4 1-0\n\n'
...     '[Event "E2"]\n\n1. d4 1-0\n'
... )
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> db.filters.all_games.get_game_row_for_index(1, "N+")
1
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_filter.py
def get_game_row_for_index(
    self, game_index: int, sort_criteria: str | bytes = "N+"
) -> int:
    """Find the sorted display row index for a specific database game index.

    Args:
        game_index: 0-based database game index.
        sort_criteria: Sorting specification string. Defaults to "N+".

    Returns:
        The 0-based display row position of the game in the sorted filter view.

    Raises:
        ValueError: If `game_index` is negative or not present in the filter,
            or if the filter has been deleted.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> pgn = (
        ...     '[Event "E1"]\\n\\n1. e4 1-0\\n\\n'
        ...     '[Event "E2"]\\n\\n1. d4 1-0\\n'
        ... )
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(pgn)
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> db.filters.all_games.get_game_row_for_index(1, "N+")
        1
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    self._check_non_negative("game_index", game_index)
    return self._native.database_filter_game_row_for_index(
        self._database._handle, self._available_id(), sort_criteria, game_index
    )

delete

delete() -> None

Delete this user-created filter and release its database resources.

Raises:

Type Description
ValueError

If attempting to delete a built-in filter (such as all_games or primary) or if the filter has already been deleted.

Examples:

>>> import tempfile, pathlib, libscid
>>> pgn = '[Event "E1"]\n\n1. e4 1-0\n'
>>> with tempfile.NamedTemporaryFile(
...     "w+", suffix=".pgn", delete=False
... ) as f:
...     _ = f.write(pgn)
...     f.flush()
...     path = f.name
>>> db = libscid.Database.open_pgn_read_only(path)
>>> custom = db.filters.create()
>>> custom.game_count
1
>>> custom.delete()
>>> db.close()
>>> pathlib.Path(path).unlink()
Source code in src/libscid/_filter.py
def delete(self) -> None:
    """Delete this user-created filter and release its database resources.

    Raises:
        ValueError: If attempting to delete a built-in filter (such as
            `all_games` or `primary`) or if the filter has already been
            deleted.

    Examples:
        >>> import tempfile, pathlib, libscid
        >>> pgn = '[Event "E1"]\\n\\n1. e4 1-0\\n'
        >>> with tempfile.NamedTemporaryFile(
        ...     "w+", suffix=".pgn", delete=False
        ... ) as f:
        ...     _ = f.write(pgn)
        ...     f.flush()
        ...     path = f.name
        >>> db = libscid.Database.open_pgn_read_only(path)
        >>> custom = db.filters.create()
        >>> custom.game_count
        1
        >>> custom.delete()
        >>> db.close()
        >>> pathlib.Path(path).unlink()
    """
    if not self._owned:
        raise ValueError("built-in filters cannot be deleted")
    self._native.database_filter_delete(
        self._database._handle, self._available_id()
    )
    self._deleted = True