Skip to content

Commit

Permalink
Allow setting the internal CCD
Browse files Browse the repository at this point in the history
  • Loading branch information
padix-key committed Nov 1, 2024
1 parent 8226e96 commit 68d8c3d
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 2 deletions.
3 changes: 2 additions & 1 deletion doc/apidoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@
],
"Low-level CCD access": [
"get_ccd",
"get_from_ccd"
"get_from_ccd",
"set_ccd_path"
]
},
"biotite.structure.io.pdbx" : {
Expand Down
40 changes: 39 additions & 1 deletion src/biotite/structure/info/ccd.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

__name__ = "biotite.structure.info"
__author__ = "Patrick Kunzmann"
__all__ = ["get_ccd", "get_from_ccd"]
__all__ = ["get_ccd", "set_ccd_path", "get_from_ccd"]

import functools
import importlib
import inspect
import pkgutil
from pathlib import Path
import numpy as np

Expand All @@ -28,6 +31,7 @@ def get_ccd():
-------
ccd : BinaryCIFBlock
The CCD.
It contains the categories `chem_comp`, `chem_comp_atom` and `chem_comp_bond`.
Warnings
--------
Expand All @@ -52,6 +56,40 @@ def get_ccd():
"Internal CCD not found. Please run 'setup_ccd.py' and reinstall Biotite."
)

def set_ccd_path(ccd_path):
"""
Replace the internal *Chemical Component Dictionary* (CCD) with a custom one.
This function also clears the cache of functions depending on the CCD to ensure
that the new CCD is used.
Parameters
----------
ccd_path : path-like
The path to the custom CCD in BinaryCIF format, prepared with the
``setup_ccd.py`` script.
Notes
-----
This function is intended for advanced users who need to add information for
compounds, which are not part of the internal CCD.
The reason might be that an updated version already exists upstream or that
the user wants to add custom compounds to the CCD.
"""
global _CCD_FILE
_CCD_FILE = Path(ccd_path)

# Clear caches in all functions in biotite.structure.info
info_modules = [
importlib.import_module(f"biotite.structure.info.{mod_name}")
for _, mod_name, _ in pkgutil.iter_modules([Path(__file__).parent])
]
for module in info_modules:
for _, function in inspect.getmembers(module, callable):
if hasattr(function, "cache_clear"):
function.cache_clear()


@functools.cache
def get_from_ccd(category_name, comp_id, column_name=None):
"""
Expand Down
33 changes: 33 additions & 0 deletions tests/structure/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,30 @@
import pytest
import biotite.structure as struc
import biotite.structure.info as strucinfo
import biotite.structure.io.pdbx as pdbx
from biotite.structure.info.ccd import _CCD_FILE as INTERNAL_CCD_FILE
from biotite.structure.io import load_structure
from tests.util import data_dir


@pytest.fixture
def fake_ccd_path(tmp_path):
block = pdbx.BinaryCIFBlock()
block["chem_comp"] = pdbx.BinaryCIFCategory({"id": "FOO", "name": "Foo"})
block["chem_comp_atom"] = pdbx.BinaryCIFCategory({"comp_id": "FOO"})
block["chem_comp_bond"] = pdbx.BinaryCIFCategory({"comp_id": "FOO"})
file = pdbx.BinaryCIFFile()
file["components"] = block
original_path = INTERNAL_CCD_FILE
path = tmp_path / "components.bcif"
file.write(path)

yield path

# Restore the original internal CCD path
strucinfo.set_ccd_path(original_path)


@pytest.mark.parametrize(
"function, included, excluded",
[
Expand Down Expand Up @@ -159,3 +179,16 @@ def test_standardize_order(multi_model, seed):
assert (
restored[..., restored.element != "H"] == original[..., original.element != "H"]
)


def test_set_ccd_path(fake_ccd_path):
"""
Test if the CCD path can be set and the CCD is loaded correctly from it.
"""
# Access CCD before setting it to a new path to check if the cache is cleared
strucinfo.all_residues()

strucinfo.set_ccd_path(fake_ccd_path)

# The new fake CCD has only a single compound
assert strucinfo.all_residues() == ["FOO"]

0 comments on commit 68d8c3d

Please sign in to comment.