<p>Here can use GraphiQL API provided by RCSB.
Here is an example</p>
<pre class="lang-py prettyprint-override"><code>import requests
from rdkit import Chem
pdbid = '7RDX'
ls_smi = []

query = f'''{{
  entry(entry_id: &quot;{pdbid}&quot;) {{
    nonpolymer_entities {{
      nonpolymer_comp {{
        rcsb_id
        pdbx_chem_comp_descriptor {{
          type
          comp_id
          program
          descriptor
        }}
      }}
    }}
  }}
}}'''
url = &quot;https://data.rcsb.org/graphql&quot;
r = requests.post(url, json={'query': query})
for i in r.json()['data']['entry']['nonpolymer_entities']:
    if i['nonpolymer_comp']['pdbx_chem_comp_descriptor'][0]['type'] == 'SMILES':
        ls_smi.append(Chem.CanonSmiles(i['nonpolymer_comp']['pdbx_chem_comp_descriptor'][0]['descriptor']))
</code></pre>

CC BY-SA 4.0
2023-03-09T22:49:42.360Z
Thanks @AustinApple and welcome to the site. A good answer
M__
CC BY-SA 4.0
2023-03-09T22:40:45.137Z
2023-03-15T21:26:13.740Z
2023-03-15T21:26:13.740Z
AustinApple
<p>OK, seems ligands and PDBs are not really a trending topic here.... (or my posts are really poor).</p>
<p>Nevertheless the question is literally eating my brain so I went into a second direction: &quot;the API&quot; already skimmed here <a href="https://bioinformatics.stackexchange.com/questions/18076/help-me-in-understanding-the-pdb-file">Help me in understanding the PDB file</a>.</p>
<p>So inspecting and copying directly from the PDB entry <a href="https://www.rcsb.org/structure/7RDX" rel="nofollow noreferrer">7RDX</a> webpage the download value: <a href="https://models.rcsb.org/v1/7rdx/ligand?auth_seq_id=1005&amp;label_asym_id=M&amp;encoding=sdf&amp;filename=7rdx_M_1N7.sdf" rel="nofollow noreferrer">https://models.rcsb.org/v1/7rdx/ligand?auth_seq_id=1005&amp;label_asym_id=M&amp;encoding=sdf&amp;filename=7rdx_M_1N7.sdf</a>I got this bit of code:</p>
<pre><code>import requests


def main():   
    risposta = requests.get(f&quot;https://models.rcsb.org/v1/7rdx/ligand?auth_seq_id=1005&amp;label_asym_id=M&amp;encoding=sdf&quot;,                      
                      allow_redirects=True)   
    open(filename, 'wb').write(risposta.content)
    print(risposta.status_code)

if __name__ == '__main__':  
   filename = '7rdx_M_1N7.sdf'   
   main()
</code></pre>
<p>That is equivalent to feeding this:</p>
<pre><code>{
  &quot;atom_site&quot;: [
    {
      &quot;auth_seq_id&quot;: 1005,
      &quot;label_asym_id&quot;: &quot;M&quot;
    }
  ]
}
</code></pre>
<p>to the 'https://models.rcsb.org/#/General/ligand-post' test webpage Request body.</p>
<p>now need to figure out how to get the <code>auth_seq_id</code> and the <code>label_asym_id </code> parameters for all the
'non_polymer_entity_ids' of my targed PDB 7RDX from the <a href="https://data.rcsb.org/rest/v1/" rel="nofollow noreferrer">https://data.rcsb.org/rest/v1/</a> DATA API.</p>
<p>Went till here:</p>
<pre><code>import requests
import json

def main(entry_id: str):
    risposta = requests.get(f&quot;https://data.rcsb.org/rest/v1/core/entry/{entry_id}/&quot;)   
    parsed = json.loads(risposta.content.decode())    
    print(risposta.status_code)    
    print((parsed))    
    print(parsed[&quot;rcsb_entry_container_identifiers&quot;][&quot;non_polymer_entity_ids&quot;])    
    print('\n', type(parsed))
    
    parsed_lig = parsed[&quot;rcsb_entry_container_identifiers&quot;][&quot;non_polymer_entity_ids&quot;]    
    print('\n\n',parsed_lig, type(parsed_lig))    
    

if __name__ == '__main__':         
   main('7rdx')
</code></pre>
<p>output :</p>
<pre><code> ['7', '8', '9', '10', '11'] &lt;class 'list'&gt;
</code></pre>
<p>The list values are the ids of the 'non_polymer_entity_ids' of my targed PDB 7RDX.</p>
<p>Right now I am trying to recover the the <code>auth_seq_id</code> and the <code>label_asym_id </code> for every instance of the 'non_polymer_entity_ids' inside my mmCIF/PDBx using the DATA API.</p>
<p>Not sure this is the fastest and right way using the RCSB PDB API to accomplish the OP question (download all ligands of the model structure (not the ligands models)) from the PDB database. Please feel free to add more or point me towards the best solution.</p>
<p><strong>EDITED:</strong></p>
<p>It took a while, I am sure this is not the right way to make good use of all the databases, there should be a way to navigate the API schemas better and faster, and to get and manage the intermediate values needed to get the two keys for the download from ModelServer &quot;Post /v1/{id}/ligand Coordinates of the first group satisfying the given criteria&quot;, anyway:</p>
<pre><code>import requests
import json

parsed_lig_dict = {}

def main(entry_id: str):    
    risposta = requests.get(f&quot;https://data.rcsb.org/rest/v1/core/entry/{entry_id}/&quot;)   
    parsed = json.loads(risposta.content.decode())    
    # print(risposta.status_code)    
    # print((parsed))    
    # print(parsed[&quot;rcsb_entry_container_identifiers&quot;][&quot;non_polymer_entity_ids&quot;])
    # print('\n', type(parsed))
    
    parsed_lig = parsed[&quot;rcsb_entry_container_identifiers&quot;][&quot;non_polymer_entity_ids&quot;]    
    # print('\n\n',parsed_lig, type(parsed_lig))
    
    # for i in parsed:        
    #     print('\n', i  , parsed[i])
                  
    for lig in parsed_lig:    
        risposta = requests.get(f&quot;https://data.rcsb.org/rest/v1/core/nonpolymer_entity/{entry_id}/{lig}&quot;)    
        parsed = json.loads(risposta.content.decode())    
        # print('\n',risposta.status_code, risposta.content)        
        
        # print(parsed[&quot;rcsb_nonpolymer_entity_container_identifiers&quot;][&quot;asym_ids&quot;], 
              # parsed[&quot;pdbx_entity_nonpoly&quot;][&quot;comp_id&quot;])
                
        parsed_lig_dict[lig] = [parsed[&quot;pdbx_entity_nonpoly&quot;][&quot;comp_id&quot;] , 
                                dict.fromkeys(parsed[&quot;rcsb_nonpolymer_entity_container_identifiers&quot;][&quot;asym_ids&quot;])]
    
    for lig in parsed_lig_dict:        
        for chain in parsed_lig_dict[lig][1]:    
            risposta = requests.get(f&quot;https://data.rcsb.org/rest/v1/core/nonpolymer_entity_instance/{entry_id}/{chain}&quot;)        
            # print(risposta.status_code)
        
            parsed = json.loads(risposta.content.decode())        
            # print('\n',risposta.status_code, risposta.content)            
            # print(chain , '--&gt; ',parsed[&quot;rcsb_nonpolymer_entity_instance_container_identifiers&quot;][&quot;auth_seq_id&quot;])
            
            parsed_lig_dict[lig][1][chain] = parsed[&quot;rcsb_nonpolymer_entity_instance_container_identifiers&quot;][&quot;auth_seq_id&quot;]        
               
    
    # print(parsed_lig_dict)
    
    # for i in parsed_lig_dict:
    #     print(parsed_lig_dict[i])
    
    # for i in parsed_lig_dict:
    #     for y in parsed_lig_dict[i][1]:
    #         print(i,parsed_lig_dict[i][0], y , parsed_lig_dict[i][1][y])
                        
    cnt = 1     
    for i in parsed_lig_dict:        
        for y in parsed_lig_dict[i][1]:
            seq_id = parsed_lig_dict[i][1][y]            
            chain = y            
            comp_id = parsed_lig_dict[i][0]            
            print(seq_id , y)

            risposta = requests.get(f&quot;https://models.rcsb.org/v1/7rdx/ligand?auth_seq_id={seq_id}&amp;label_asym_id={chain}&amp;encoding=sdf&quot; ,                              
                              allow_redirects=True)             
            print(risposta.status_code)
                        
            filename = f&quot;lig_{comp_id}_{chain}_{seq_id}_{cnt}.sdf&quot;            
            open(filename, 'wb').write(risposta.content)            
            cnt += 1

    
if __name__ == '__main__':         
   main('7rdx')
</code></pre>
<p>This approach is very slow on my machine, the other two answers that parse the downloaded full mmCIF file via Biopython Structure Object are faster even adding the translation <code>.sdf</code> files to SMILES via Openbabel. I think this is because I translate the DATA API JSON formatted schema into a dictionary to extract the data I need, there should be another way to make use of that data. This way doesn't need installed Biopython though.</p>

CC BY-SA 4.0
2023-01-08T13:05:33.160Z
2023-03-01T14:26:12.993Z
2023-03-01T14:26:12.993Z
pippo1980
<p>Ok, cannot try the API approach, no resources during vacation. Got the download and parse/split approach, added conversion to SMILES by <strong>Openbabel</strong>. I believe it is exactly the same question of : <a href="https://stackoverflow.com/questions/61390035/how-to-save-each-ligand-from-a-pdb-file-separately-with-bio-pdb">https://stackoverflow.com/questions/61390035/how-to-save-each-ligand-from-a-pdb-file-separately-with-bio-pdb</a> from where I stole the juicy bit of the code:</p>
<pre><code>## stealing from https://stackoverflow.com/questions/61390035/how-to-save-each-ligand-from-a-pdb-file-separately-with-bio-pdb

from Bio import __version__


print('BIOPYTHON Version : ' , __version__)

from Bio.PDB import MMCIFParser, PDBIO, Select, PDBList

import subprocess

filename = '7rdx.cif'


pdblist = PDBList()

pdblist.retrieve_pdb_file(filename.upper().split('.')[0] , pdir = '.')


pars = MMCIFParser(QUIET = True)

struct = pars.get_structure('7rdx',  filename)


print(struct, type(struct))


def is_het(residue):
    res = residue.id[0]
    
    if res != &quot; &quot; and res != &quot;W&quot;:
        
        return True #1

    else:
    
        return False #0
 

class ResidueSelect(Select):
    def __init__(self, chain, residue):
        self.chain = chain
        self.residue = residue

    def accept_chain(self, chain):
    
        if chain.id == self.chain.id:
            
            return True #1
        
        else:
            
            return False #0
        
        
    def accept_residue(self, residue):
        &quot;&quot;&quot; Recognition of heteroatoms - Remove water molecules &quot;&quot;&quot;
        
        if residue == self.residue and is_het(residue):
            
            return True #1
        
        else:
            
            return False #0


for model in struct :
    
    
    dizio = model.child_dict
    
    print('\n'+ str(dizio))
    
    

for model in struct:
    
    i = 1
    
    print(model, model.id)
    
    for chain in model:
        
        print(chain, chain.id)
        
        for res in chain:
            
            # print('\n'+'res : ', res, 'res.resname : ', res.resname)
            
            if res.id[0] != &quot; &quot; :
                
                
                print('\n'+'res : ', res, res.id, res.resname)
                
                io = PDBIO()
                
                io.set_structure(struct)
                
                io.save(f&quot;lig_{chain.id}_{res.resname}_{i}.pdb&quot;, ResidueSelect(chain, res))
                
                try:
                    subprocess.run([&quot;openbabel.obabel&quot;, &quot;-ipdb&quot;, f&quot;lig_{chain.id}_{res.resname}_{i}.pdb&quot;, &quot;-osmi&quot; ,&quot;-O&quot;,
                                    
                                    f&quot;lig_{chain.id}_{res.resname}_{i}.smi&quot;])
                    

                    
                except Exception as excp:
                    
                    print('\n ', excp, '\n')
                    
                    
                
                i += 1

</code></pre>
<p>Problem is Biopython parses <strong>mmCIF</strong>s files; that are now the official standard for RCSB PDB <a href="https://www.rcsb.org/news/feature/5c767ef5ea7d0653b99c876f" rel="nofollow noreferrer"><strong>Submission of PDBx/mmCIF format files for crystallographic depositions to the PDB will be mandatory from July 1st 2019 onward</strong>. PDB format files will no longer be accepted for deposition of structures solved by MX techniques.</a> A nice review about the long history of the 'new' format :[ <a href="https://www.sciencedirect.com/science/article/pii/S0022283622001796?via%3Dihub" rel="nofollow noreferrer">PDBx/mmCIF Ecosystem: Foundational Semantic Tools for Structural Biology</a>.</p>
<p>Unfortunately the <code>Bio.PDB.MMCIFParser module</code> return a a structure that is:</p>
<p><code>&lt;Structure id=7rdx&gt; &lt;class 'Bio.PDB.Structure.Structure'&gt;</code> so all the ligands are related to PDB chains classification and not PDBx entity.</p>
<p>I could be wrong here, I am a novice in both Biopython and Structural Bioinformatics but this is what I've noticed, please feel free to elaborate on this and add a solution using the RCSB APIes ... before curiosity kills the cat</p>
<p>PS:</p>
<p><strong>NEEDs to be tested with sample containing water</strong></p>

CC BY-SA 4.0
2023-01-05T18:04:11.050Z
2023-01-05T18:26:24.320Z
2023-01-05T18:26:24.320Z
pippo1980
<p>adding a different way to solve the question , still without using RCSB PDB database, but using <code>Biopython.PDB.MMCIF2Dict</code> module to start to get a feeling on how info are stored in .cif format files. Ligands are selected amongs the ones in the structure for their Mw.</p>
<p>Ligands are then saved using the same approach of first answer or a new one that takes advantage of <code>Biopython.PDB.Dice</code> module to extract and save portion of PDB. Again Openbabel is used to translate PDBs to SMILES.</p>
<p>Code :</p>
<pre><code>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Created on Thu Jan  5 11:43:07 2023

@author: bob

https://bioinformatics.stackexchange.com/questions/20301/how-to-download-ligands-for-pdb-structure?noredirect=1#comment29972_20301




NEEDs to be tested with sample containing water


&quot;&quot;&quot;

## stealing from https://stackoverflow.com/questions/61390035/how-to-save-each-ligand-from-a-pdb-file-separately-with-bio-pdb

from Bio import __version__


print('BIOPYTHON Version : ' , __version__)

from Bio.PDB import (
                    MMCIFParser, 
                    PDBIO, 
                    Select, 
                    PDBList, 
                    MMCIF2Dict,
                    Dice
                    )

import subprocess

filename = '7rdx.cif'

# filename = '7RDX.cif'  # this will  work , but biopython saves in lower case 


# pdblist = PDBList()

# pdblist.retrieve_pdb_file(filename.split('.')[0] , pdir = '.')


pars = MMCIFParser(QUIET = True)

struct = pars.get_structure('7rdx',  filename)


# print(struct, type(struct))


mmcifdict = MMCIF2Dict.MMCIF2Dict(filename)


# print(mmcifdict)

# for i in mmcifdict:
    
#     print('\n', i ,'   ---&gt; ', mmcifdict[i])
    
print('##############################################################')
    
# print(mmcifdict['_entity'])

 
print(mmcifdict['_pdbx_nonpoly_scheme.entity_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.asym_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.entity_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.mon_id'])

ligands_map_mmcif = list(zip(mmcifdict['_pdbx_nonpoly_scheme.entity_id'],
                             
                             mmcifdict['_pdbx_nonpoly_scheme.asym_id'],
                             
                             mmcifdict['_pdbx_nonpoly_scheme.entity_id'],
                             
                             mmcifdict['_pdbx_nonpoly_scheme.mon_id']))


print('#########################################')

print(ligands_map_mmcif)

print(mmcifdict['_pdbx_nonpoly_scheme.entity_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.pdb_seq_num'])
print(mmcifdict['_pdbx_nonpoly_scheme.pdb_strand_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.pdb_mon_id'])

ligands_map_pdb = list(zip(mmcifdict['_pdbx_nonpoly_scheme.entity_id'],
                           
                           mmcifdict['_pdbx_nonpoly_scheme.pdb_seq_num'],
                           
                           mmcifdict['_pdbx_nonpoly_scheme.pdb_strand_id'],
                           
                           mmcifdict['_pdbx_nonpoly_scheme.pdb_mon_id']))

print('#########################################')

print(ligands_map_pdb)


print('WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWw')

print(mmcifdict['_entity.id'])
print(mmcifdict['_entity.formula_weight'])

entity_Mw = dict(zip(mmcifdict['_entity.id'],
                     
                     mmcifdict['_entity.formula_weight']))

print(entity_Mw)

print('-------------------------------------------------------------')


ligands_map_pdb_red = [i for i in ligands_map_pdb if float(entity_Mw[i[0]]) &gt;= 200 ]

print(ligands_map_pdb_red)





#########################################################   first approach 


# class ResidueSelect(Select):
#     def __init__(self, chain, residue):
#         self.chain = chain
#         self.residue = residue

#     def accept_chain(self, chain):
    
#       if chain.id == self.chain.id:
            
#           return True #1
        
#       else:
            
#           return False #0
        
        
#     def accept_residue(self, residue):
#         &quot;&quot;&quot; Recognition of heteroatoms - Remove water molecules &quot;&quot;&quot;
        
#         if residue == self.residue :
            
            
#             return True #1
        
#         else:
            
#             return False #0




# for model in struct:
    
#     print(model)
    
#     for chain in model:
        
#         if chain.id == 'A':
            
#             for res in chain:
                
#                 print(res, res.id, res.get_resname(), type(res))

# i = 1    

# for res in ligands_map_pdb_red :
    
#     print('\n'+'res : ', res[3], res[0], res[1], type(res[1]), res[2])
    
#     print(struct[0][res[2]] , type(struct[0][res[2]]) )
    
#     print(struct[0][res[2]][('H_'+res[3], int(res[1]), ' ')] , type(struct[0][res[2]][('H_'+res[3], int(res[1]), ' ')]) )
    
                
#     io = PDBIO()
                    
#     io.set_structure(struct)
                    
#     io.save(f&quot;lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.pdb&quot;, ResidueSelect(struct[0][res[2]],
                                                                          
#                                                     struct[0][res[2]][('H_'+res[3], int(res[1]), ' ')]))
                    
#     try:
#         subprocess.run([&quot;openbabel.obabel&quot;, &quot;-ipdb&quot;, f&quot;lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.pdb&quot;, &quot;-osmi&quot; ,&quot;-O&quot;,
                                        
#         f&quot;lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.smi&quot;])
                        
    
                        
#     except Exception as excp:
                        
#         print('\n ', excp, '\n')
                        
                        
                    
#     i += 1
    
########################################################   second approach 


import warnings

from Bio import BiopythonWarning


def accept_residue_mod(self, residue):
        &quot;&quot;&quot;Verify if a residue sequence is between the start and end sequence.&quot;&quot;&quot;
        # residue - between start and end
        hetatm_flag, resseq, icode = residue.get_id()
        # if hetatm_flag != &quot; &quot;:
        #     # skip HETATMS
        #     return 0
        if icode != &quot; &quot;:
            warnings.warn(
                f&quot;WARNING: Icode {icode} at position {resseq}&quot;, BiopythonWarning
            )
        if self.start &lt;= resseq &lt;= self.end:
            return 1
        return 0

Dice.ChainSelector.accept_residue = accept_residue_mod


i = 1

for res in ligands_map_pdb_red :
    
    print(struct, res[2], int(res[1]), int(res[1]))

    Dice.extract(struct, res[2], int(res[1]), int(res[1]), f&quot;lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.pdb&quot;)
    
    
    try:
        subprocess.run([&quot;openbabel.obabel&quot;, &quot;-ipdb&quot;, f&quot;lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.pdb&quot;, &quot;-osmi&quot; ,&quot;-O&quot;,
                                           
        f&quot;lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.smi&quot;])
                            
        
    except Exception as excp:
                            
        print('\n ', excp, '\n')
                            
                            
                        
    i += 1

<span class="math-container">```</span>
</code></pre>

CC BY-SA 4.0
2023-01-07T00:30:39.627Z
2023-01-08T15:20:45.277Z
2023-01-08T15:20:45.277Z
pippo1980
<p>I have some PDB IDs and for every structure, I need all its ligands, so I want to automate the process.</p>
<p>The ligands are different for every chain:</p>
<pre><code>$ cat ./7rdx_N_1N7.smi 
C1[C@@]2(C)[C@@H](C[C@@H](C1)O)[C][C]([C]1[C@H]3[C@]([C@H](C[C]21)O)([C@H](CC3)[C]([C])[C][C])C)O   1N7
$ cat ./7rdx_U_1N7.smi 
C1[C@@]2(C)[C@@H](C[C@@H](C1)O)C[C@H]([C@H]1[C@H]3[C@]([C@H](C[C@H]21)O)([C@H](CC3)[C@H](C)CCC(=O)NC[C][C][N+]([C])([C])[C])C)O 1N7
</code></pre>
<p>So, I cannot just download the ligands by its name (e.g. <code>https://files.rcsb.org/ligands/download/ZN_ideal.sdf</code>).</p>
<p>I've tried to find the structures in <em>.pdb</em> file for a given structure, but I see only their names:<br />
<a href="https://i.sstatic.net/bgwRX.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/bgwRX.png" alt="pdb_file_part" /></a></p>
<p>Also, as you could see above, I'll convert the <em>.sdf</em> / <em>.mol2</em> files to <em>SMARTS</em> which seems to be a simpler representation - maybe this information'll make the task easier.</p>
<hr />
<p>I want to make a script for the process in <em>Python</em>, and the only way to access the <em>RSCB PDB</em> with a library I've found was using <code>biopython</code> but I found there an option to download just a <em>.pdb</em> file (<code>Bio.PDB.PDBList.retrieve_pdb_file</code>), not ligands.</p>
<p>I've also tried to make my own script, but still I don't know how to access ligands files automatically for every structure. The <em>.pdb</em> files I can access simply by <code>ftp</code> (e.g. <code>https://ftp.wwpdb.org/pub/pdb/data/biounit/PDB/divided/rd/</code>).</p>

CC BY-SA 4.0
2023-01-02T17:03:17.960Z
How to save each ligand from a PDB file separately with Bio.PDB?  ---> https://stackoverflow.com/questions/61390035/how-to-save-each-ligand-from-a-pdb-file-separately-with-bio-pdb
pippo1980
CC BY-SA 4.0
2023-01-02T17:07:30.723Z
have a look at ModelServer API too https://models.rcsb.org/#/ and for Pyhton https://github.com/OpenAPITools/openapi-generator
pippo1980
CC BY-SA 4.0
2023-01-02T18:24:02.923Z
there is a (new for me) https://data.rcsb.org/graphql/index.html GraphQL API : The GraphQL interface offers more flexible data retrieval, essentially making it possible to grab any piece of data from any level of the hierarchy in a single query. To use it programmatically POST your GraphQL queries under the data.rcsb.org/graphql endpoint. No idea how it works but should be the one you are looking for to download all Ligands ??
pippo1980
CC BY-SA 4.0
2023-01-02T18:48:04.363Z
dont understand what does:  " /v1/{id}/ligand
Coordinates of the first group satisfying the given criteria."  means for the ModelServerAPI https://models.rcsb.org/#/
pippo1980
CC BY-SA 4.0
2023-01-04T08:11:57.850Z
Thank you for your response. It seems they meant first ligand for the given structure, but not unique one. I tired it on *7RDZ* and got *ASP* which is not an unique ligand for the structure.
Still trying to get all unique ligands.
maciejwww
CC BY-SA 4.0
2023-01-04T08:31:52.060Z
Played a little bit on modelserver with no luck, graphQL should be easier because the test page has autocompletion  but wont work with OpenAPI generator for Python bindings. Ligands used to belong to a Chain in the old PDBs standard now they are non polymer entity or chemical compound so buffer components get mixed up with 'proper' ligands. Need to figure out how to get these entity out from API just sending entry ID and then recover them one by one (for all the Asymmetric units !?) ; not sure it'll be a,less hassle then parsing the full PDBx/mmCIF file
pippo1980
CC BY-SA 4.0
2023-01-04T11:25:30.300Z
ADDENDUM https://www.apollographql.com/blog/graphql/python/complete-api-guide/ : Using GraphQL with Python – A Complete Guide
pippo1980
CC BY-SA 4.0
2023-01-04T18:57:32.363Z
Thanks, I'll try to use a query I've made earlier in *GraphiQL*. It seems to provide the ligands in *.smi* format right away.
Still, I'm curious if it's possible to get the files from the webpage (*.sdf*, *.mol2*) with any *GrapgQL* query.
maciejwww
CC BY-SA 4.0
2023-01-04T20:45:24.273Z
Found this on rcsb pdb : Ligand of Interest (LOI)
A small molecule is termed a Ligand of Interest (LOI) when it is the subject of the author’s research rather than a byproduct of the experimental conditions. For example, in entry 1OBV, the small molecule palmitic acid. (PLM) is designated as an LOI as it is bound inside the cavity of the the fatty-acid-binding protein EgFABP1 and is integral to the study. Now need to figure out where LOI is written
pippo1980
CC BY-SA 4.0
2023-01-04T20:54:51.487Z
Pdb 101 has a typo example pdb is 1O8B not 1OBV
pippo1980
CC BY-SA 4.0
2023-01-04T20:59:57.543Z
More, I am learning mmcif :  residues/ligands do not have a residue number in mmCIF
pippo1980
CC BY-SA 4.0
2023-01-08T09:31:31.077Z
my memoruy is failing please find this post I tried to answer about 1 year ago https://bioinformatics.stackexchange.com/questions/18076/help-me-in-understanding-the-pdb-file
pippo1980
<python><pdb><data-download>
How to download ligands for PDB structure
# How to download ligands for PDB structure

(Asked by: username_0 on 2023-01-02)

I have some PDB IDs and for every structure, I need all its ligands, so I want to automate the process.
The ligands are different for every chain:
$ cat ./7rdx_N_1N7.smi 
C1[C@@]2(C)[C@@H](C[C@@H](C1)O)[C][C]([C]1[C@H]3[C@]([C@H](C[C]21)O)([C@H](CC3)[C]([C])[C][C])C)O   1N7
$ cat ./7rdx_U_1N7.smi 
C1[C@@]2(C)[C@@H](C[C@@H](C1)O)C[C@H]([C@H]1[C@H]3[C@]([C@H](C[C@H]21)O)([C@H](CC3)[C@H](C)CCC(=O)NC[C][C][N+]([C])([C])[C])C)O 1N7

So, I cannot just download the ligands by its name (e.g. https://files.rcsb.org/ligands/download/ZN_ideal.sdf).
I've tried to find the structures in .pdb file for a given structure, but I see only their names:

Also, as you could see above, I'll convert the .sdf / .mol2 files to SMARTS which seems to be a simpler representation - maybe this information'll make the task easier.

I want to make a script for the process in Python, and the only way to access the RSCB PDB with a library I've found was using biopython but I found there an option to download just a .pdb file (Bio.PDB.PDBList.retrieve_pdb_file), not ligands.
I've also tried to make my own script, but still I don't know how to access ligands files automatically for every structure. The .pdb files I can access simply by ftp (e.g. https://ftp.wwpdb.org/pub/pdb/data/biounit/PDB/divided/rd/).

**Tags:** python, pdb, data-download
> **Comments:**
> **username_1**: How to save each ligand from a PDB file separately with Bio.PDB?  ---> https://stackoverflow.com/questions/61390035/how-to-save-each-ligand-from-a-pdb-file-separately-with-bio-pdb  
> **username_1**: have a look at ModelServer API too https://models.rcsb.org/#/ and for Pyhton https://github.com/OpenAPITools/openapi-generator  
> **username_1**: there is a (new for me) https://data.rcsb.org/graphql/index.html GraphQL API : The GraphQL interface offers more flexible data retrieval, essentially making it possible to grab any piece of data from any level of the hierarchy in a single query. To use it programmatically POST your GraphQL queries under the data.rcsb.org/graphql endpoint. No idea how it works but should be the one you are looking for to download all Ligands ??  
> **username_1**: dont understand what does:  " /v1/{id}/ligand Coordinates of the first group satisfying the given criteria."  means for the ModelServerAPI https://models.rcsb.org/#/  
> **username_0**: Thank you for your response. It seems they meant first ligand for the given structure, but not unique one. I tired it on *7RDZ* and got *ASP* which is not an unique ligand for the structure. Still trying to get all unique ligands.  
> **username_1**: Played a little bit on modelserver with no luck, graphQL should be easier because the test page has autocompletion  but wont work with OpenAPI generator for Python bindings. Ligands used to belong to a Chain in the old PDBs standard now they are non polymer entity or chemical compound so buffer components get mixed up with 'proper' ligands. Need to figure out how to get these entity out from API just sending entry ID and then recover them one by one (for all the Asymmetric units !?) ; not sure it'll be a,less hassle then parsing the full PDBx/mmCIF file  
> **username_1**: ADDENDUM https://www.apollographql.com/blog/graphql/python/complete-api-guide/ : Using GraphQL with Python – A Complete Guide  
> **username_0**: Thanks, I'll try to use a query I've made earlier in *GraphiQL*. It seems to provide the ligands in *.smi* format right away. Still, I'm curious if it's possible to get the files from the webpage (*.sdf*, *.mol2*) with any *GrapgQL* query.  
> **username_1**: Found this on rcsb pdb : Ligand of Interest (LOI) A small molecule is termed a Ligand of Interest (LOI) when it is the subject of the author’s research rather than a byproduct of the experimental conditions. For example, in entry 1OBV, the small molecule palmitic acid. (PLM) is designated as an LOI as it is bound inside the cavity of the the fatty-acid-binding protein EgFABP1 and is integral to the study. Now need to figure out where LOI is written  
> **username_1**: Pdb 101 has a typo example pdb is 1O8B not 1OBV  
> **username_1**: More, I am learning mmcif :  residues/ligands do not have a residue number in mmCIF  
> **username_1**: my memoruy is failing please find this post I tried to answer about 1 year ago https://bioinformatics.stackexchange.com/questions/18076/help-me-in-understanding-the-pdb-file  

---

## 4 answers

### Answer by username_2 on 2023-03-09. Score: 1

Here can use GraphiQL API provided by RCSB.
Here is an example
import requests
from rdkit import Chem
pdbid = '7RDX'
ls_smi = []

query = f'''{{
  entry(entry_id: "{pdbid}") {{
    nonpolymer_entities {{
      nonpolymer_comp {{
        rcsb_id
        pdbx_chem_comp_descriptor {{
          type
          comp_id
          program
          descriptor
        }}
      }}
    }}
  }}
}}'''
url = "https://data.rcsb.org/graphql"
r = requests.post(url, json={'query': query})
for i in r.json()['data']['entry']['nonpolymer_entities']:
    if i['nonpolymer_comp']['pdbx_chem_comp_descriptor'][0]['type'] == 'SMILES':
        ls_smi.append(Chem.CanonSmiles(i['nonpolymer_comp']['pdbx_chem_comp_descriptor'][0]['descriptor']))

> **Comments:**
> **username_3**: Thanks @AustinApple and welcome to the site. A good answer  

---

### Answer by username_1 on 2023-01-08. Score: 1

OK, seems ligands and PDBs are not really a trending topic here.... (or my posts are really poor).
Nevertheless the question is literally eating my brain so I went into a second direction: "the API" already skimmed here Help me in understanding the PDB file.
So inspecting and copying directly from the PDB entry 7RDX webpage the download value: https://models.rcsb.org/v1/7rdx/ligand?auth_seq_id=1005&label_asym_id=M&encoding=sdf&filename=7rdx_M_1N7.sdfI got this bit of code:
import requests


def main():   
    risposta = requests.get(f"https://models.rcsb.org/v1/7rdx/ligand?auth_seq_id=1005&label_asym_id=M&encoding=sdf",                      
                      allow_redirects=True)   
    open(filename, 'wb').write(risposta.content)
    print(risposta.status_code)

if __name__ == '__main__':  
   filename = '7rdx_M_1N7.sdf'   
   main()

That is equivalent to feeding this:
{
  "atom_site": [
    {
      "auth_seq_id": 1005,
      "label_asym_id": "M"
    }
  ]
}

to the 'https://models.rcsb.org/#/General/ligand-post' test webpage Request body.
now need to figure out how to get the auth_seq_id and the label_asym_id  parameters for all the
'non_polymer_entity_ids' of my targed PDB 7RDX from the https://data.rcsb.org/rest/v1/ DATA API.
Went till here:
import requests
import json

def main(entry_id: str):
    risposta = requests.get(f"https://data.rcsb.org/rest/v1/core/entry/{entry_id}/")   
    parsed = json.loads(risposta.content.decode())    
    print(risposta.status_code)    
    print((parsed))    
    print(parsed["rcsb_entry_container_identifiers"]["non_polymer_entity_ids"])    
    print('\n', type(parsed))
    
    parsed_lig = parsed["rcsb_entry_container_identifiers"]["non_polymer_entity_ids"]    
    print('\n\n',parsed_lig, type(parsed_lig))    
    

if __name__ == '__main__':         
   main('7rdx')

output :
 ['7', '8', '9', '10', '11'] 

The list values are the ids of the 'non_polymer_entity_ids' of my targed PDB 7RDX.
Right now I am trying to recover the the auth_seq_id and the label_asym_id  for every instance of the 'non_polymer_entity_ids' inside my mmCIF/PDBx using the DATA API.
Not sure this is the fastest and right way using the RCSB PDB API to accomplish the OP question (download all ligands of the model structure (not the ligands models)) from the PDB database. Please feel free to add more or point me towards the best solution.
EDITED:
It took a while, I am sure this is not the right way to make good use of all the databases, there should be a way to navigate the API schemas better and faster, and to get and manage the intermediate values needed to get the two keys for the download from ModelServer "Post /v1/{id}/ligand Coordinates of the first group satisfying the given criteria", anyway:
import requests
import json

parsed_lig_dict = {}

def main(entry_id: str):    
    risposta = requests.get(f"https://data.rcsb.org/rest/v1/core/entry/{entry_id}/")   
    parsed = json.loads(risposta.content.decode())    
    # print(risposta.status_code)    
    # print((parsed))    
    # print(parsed["rcsb_entry_container_identifiers"]["non_polymer_entity_ids"])
    # print('\n', type(parsed))
    
    parsed_lig = parsed["rcsb_entry_container_identifiers"]["non_polymer_entity_ids"]    
    # print('\n\n',parsed_lig, type(parsed_lig))
    
    # for i in parsed:        
    #     print('\n', i  , parsed[i])
                  
    for lig in parsed_lig:    
        risposta = requests.get(f"https://data.rcsb.org/rest/v1/core/nonpolymer_entity/{entry_id}/{lig}")    
        parsed = json.loads(risposta.content.decode())    
        # print('\n',risposta.status_code, risposta.content)        
        
        # print(parsed["rcsb_nonpolymer_entity_container_identifiers"]["asym_ids"], 
              # parsed["pdbx_entity_nonpoly"]["comp_id"])
                
        parsed_lig_dict[lig] = [parsed["pdbx_entity_nonpoly"]["comp_id"] , 
                                dict.fromkeys(parsed["rcsb_nonpolymer_entity_container_identifiers"]["asym_ids"])]
    
    for lig in parsed_lig_dict:        
        for chain in parsed_lig_dict[lig][1]:    
            risposta = requests.get(f"https://data.rcsb.org/rest/v1/core/nonpolymer_entity_instance/{entry_id}/{chain}")        
            # print(risposta.status_code)
        
            parsed = json.loads(risposta.content.decode())        
            # print('\n',risposta.status_code, risposta.content)            
            # print(chain , '--> ',parsed["rcsb_nonpolymer_entity_instance_container_identifiers"]["auth_seq_id"])
            
            parsed_lig_dict[lig][1][chain] = parsed["rcsb_nonpolymer_entity_instance_container_identifiers"]["auth_seq_id"]        
               
    
    # print(parsed_lig_dict)
    
    # for i in parsed_lig_dict:
    #     print(parsed_lig_dict[i])
    
    # for i in parsed_lig_dict:
    #     for y in parsed_lig_dict[i][1]:
    #         print(i,parsed_lig_dict[i][0], y , parsed_lig_dict[i][1][y])
                        
    cnt = 1     
    for i in parsed_lig_dict:        
        for y in parsed_lig_dict[i][1]:
            seq_id = parsed_lig_dict[i][1][y]            
            chain = y            
            comp_id = parsed_lig_dict[i][0]            
            print(seq_id , y)

            risposta = requests.get(f"https://models.rcsb.org/v1/7rdx/ligand?auth_seq_id={seq_id}&label_asym_id={chain}&encoding=sdf" ,                              
                              allow_redirects=True)             
            print(risposta.status_code)
                        
            filename = f"lig_{comp_id}_{chain}_{seq_id}_{cnt}.sdf"            
            open(filename, 'wb').write(risposta.content)            
            cnt += 1

    
if __name__ == '__main__':         
   main('7rdx')

This approach is very slow on my machine, the other two answers that parse the downloaded full mmCIF file via Biopython Structure Object are faster even adding the translation .sdf files to SMILES via Openbabel. I think this is because I translate the DATA API JSON formatted schema into a dictionary to extract the data I need, there should be another way to make use of that data. This way doesn't need installed Biopython though.


---

### Answer by username_1 on 2023-01-05. Score: 1

Ok, cannot try the API approach, no resources during vacation. Got the download and parse/split approach, added conversion to SMILES by Openbabel. I believe it is exactly the same question of : https://stackoverflow.com/questions/61390035/how-to-save-each-ligand-from-a-pdb-file-separately-with-bio-pdb from where I stole the juicy bit of the code:
## stealing from https://stackoverflow.com/questions/61390035/how-to-save-each-ligand-from-a-pdb-file-separately-with-bio-pdb

from Bio import __version__


print('BIOPYTHON Version : ' , __version__)

from Bio.PDB import MMCIFParser, PDBIO, Select, PDBList

import subprocess

filename = '7rdx.cif'


pdblist = PDBList()

pdblist.retrieve_pdb_file(filename.upper().split('.')[0] , pdir = '.')


pars = MMCIFParser(QUIET = True)

struct = pars.get_structure('7rdx',  filename)


print(struct, type(struct))


def is_het(residue):
    res = residue.id[0]
    
    if res != " " and res != "W":
        
        return True #1

    else:
    
        return False #0
 

class ResidueSelect(Select):
    def __init__(self, chain, residue):
        self.chain = chain
        self.residue = residue

    def accept_chain(self, chain):
    
        if chain.id == self.chain.id:
            
            return True #1
        
        else:
            
            return False #0
        
        
    def accept_residue(self, residue):
        """ Recognition of heteroatoms - Remove water molecules """
        
        if residue == self.residue and is_het(residue):
            
            return True #1
        
        else:
            
            return False #0


for model in struct :
    
    
    dizio = model.child_dict
    
    print('\n'+ str(dizio))
    
    

for model in struct:
    
    i = 1
    
    print(model, model.id)
    
    for chain in model:
        
        print(chain, chain.id)
        
        for res in chain:
            
            # print('\n'+'res : ', res, 'res.resname : ', res.resname)
            
            if res.id[0] != " " :
                
                
                print('\n'+'res : ', res, res.id, res.resname)
                
                io = PDBIO()
                
                io.set_structure(struct)
                
                io.save(f"lig_{chain.id}_{res.resname}_{i}.pdb", ResidueSelect(chain, res))
                
                try:
                    subprocess.run(["openbabel.obabel", "-ipdb", f"lig_{chain.id}_{res.resname}_{i}.pdb", "-osmi" ,"-O",
                                    
                                    f"lig_{chain.id}_{res.resname}_{i}.smi"])
                    

                    
                except Exception as excp:
                    
                    print('\n ', excp, '\n')
                    
                    
                
                i += 1


Problem is Biopython parses mmCIFs files; that are now the official standard for RCSB PDB Submission of PDBx/mmCIF format files for crystallographic depositions to the PDB will be mandatory from July 1st 2019 onward. PDB format files will no longer be accepted for deposition of structures solved by MX techniques. A nice review about the long history of the 'new' format :[ PDBx/mmCIF Ecosystem: Foundational Semantic Tools for Structural Biology.
Unfortunately the Bio.PDB.MMCIFParser module return a a structure that is:
  so all the ligands are related to PDB chains classification and not PDBx entity.
I could be wrong here, I am a novice in both Biopython and Structural Bioinformatics but this is what I've noticed, please feel free to elaborate on this and add a solution using the RCSB APIes ... before curiosity kills the cat
PS:
NEEDs to be tested with sample containing water


---

### Answer by username_1 on 2023-01-07. Score: 1

adding a different way to solve the question , still without using RCSB PDB database, but using Biopython.PDB.MMCIF2Dict module to start to get a feeling on how info are stored in .cif format files. Ligands are selected amongs the ones in the structure for their Mw.
Ligands are then saved using the same approach of first answer or a new one that takes advantage of Biopython.PDB.Dice module to extract and save portion of PDB. Again Openbabel is used to translate PDBs to SMILES.
Code :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Jan  5 11:43:07 2023

@author: bob

https://bioinformatics.stackexchange.com/questions/20301/how-to-download-ligands-for-pdb-structure?noredirect=1#comment29972_20301




NEEDs to be tested with sample containing water


"""

## stealing from https://stackoverflow.com/questions/61390035/how-to-save-each-ligand-from-a-pdb-file-separately-with-bio-pdb

from Bio import __version__


print('BIOPYTHON Version : ' , __version__)

from Bio.PDB import (
                    MMCIFParser, 
                    PDBIO, 
                    Select, 
                    PDBList, 
                    MMCIF2Dict,
                    Dice
                    )

import subprocess

filename = '7rdx.cif'

# filename = '7RDX.cif'  # this will  work , but biopython saves in lower case 


# pdblist = PDBList()

# pdblist.retrieve_pdb_file(filename.split('.')[0] , pdir = '.')


pars = MMCIFParser(QUIET = True)

struct = pars.get_structure('7rdx',  filename)


# print(struct, type(struct))


mmcifdict = MMCIF2Dict.MMCIF2Dict(filename)


# print(mmcifdict)

# for i in mmcifdict:
    
#     print('\n', i ,'   ---> ', mmcifdict[i])
    
print('##############################################################')
    
# print(mmcifdict['_entity'])

 
print(mmcifdict['_pdbx_nonpoly_scheme.entity_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.asym_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.entity_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.mon_id'])

ligands_map_mmcif = list(zip(mmcifdict['_pdbx_nonpoly_scheme.entity_id'],
                             
                             mmcifdict['_pdbx_nonpoly_scheme.asym_id'],
                             
                             mmcifdict['_pdbx_nonpoly_scheme.entity_id'],
                             
                             mmcifdict['_pdbx_nonpoly_scheme.mon_id']))


print('#########################################')

print(ligands_map_mmcif)

print(mmcifdict['_pdbx_nonpoly_scheme.entity_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.pdb_seq_num'])
print(mmcifdict['_pdbx_nonpoly_scheme.pdb_strand_id'])
print(mmcifdict['_pdbx_nonpoly_scheme.pdb_mon_id'])

ligands_map_pdb = list(zip(mmcifdict['_pdbx_nonpoly_scheme.entity_id'],
                           
                           mmcifdict['_pdbx_nonpoly_scheme.pdb_seq_num'],
                           
                           mmcifdict['_pdbx_nonpoly_scheme.pdb_strand_id'],
                           
                           mmcifdict['_pdbx_nonpoly_scheme.pdb_mon_id']))

print('#########################################')

print(ligands_map_pdb)


print('WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWw')

print(mmcifdict['_entity.id'])
print(mmcifdict['_entity.formula_weight'])

entity_Mw = dict(zip(mmcifdict['_entity.id'],
                     
                     mmcifdict['_entity.formula_weight']))

print(entity_Mw)

print('-------------------------------------------------------------')


ligands_map_pdb_red = [i for i in ligands_map_pdb if float(entity_Mw[i[0]]) >= 200 ]

print(ligands_map_pdb_red)





#########################################################   first approach 


# class ResidueSelect(Select):
#     def __init__(self, chain, residue):
#         self.chain = chain
#         self.residue = residue

#     def accept_chain(self, chain):
    
#       if chain.id == self.chain.id:
            
#           return True #1
        
#       else:
            
#           return False #0
        
        
#     def accept_residue(self, residue):
#         """ Recognition of heteroatoms - Remove water molecules """
        
#         if residue == self.residue :
            
            
#             return True #1
        
#         else:
            
#             return False #0




# for model in struct:
    
#     print(model)
    
#     for chain in model:
        
#         if chain.id == 'A':
            
#             for res in chain:
                
#                 print(res, res.id, res.get_resname(), type(res))

# i = 1    

# for res in ligands_map_pdb_red :
    
#     print('\n'+'res : ', res[3], res[0], res[1], type(res[1]), res[2])
    
#     print(struct[0][res[2]] , type(struct[0][res[2]]) )
    
#     print(struct[0][res[2]][('H_'+res[3], int(res[1]), ' ')] , type(struct[0][res[2]][('H_'+res[3], int(res[1]), ' ')]) )
    
                
#     io = PDBIO()
                    
#     io.set_structure(struct)
                    
#     io.save(f"lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.pdb", ResidueSelect(struct[0][res[2]],
                                                                          
#                                                     struct[0][res[2]][('H_'+res[3], int(res[1]), ' ')]))
                    
#     try:
#         subprocess.run(["openbabel.obabel", "-ipdb", f"lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.pdb", "-osmi" ,"-O",
                                        
#         f"lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.smi"])
                        
    
                        
#     except Exception as excp:
                        
#         print('\n ', excp, '\n')
                        
                        
                    
#     i += 1
    
########################################################   second approach 


import warnings

from Bio import BiopythonWarning


def accept_residue_mod(self, residue):
        """Verify if a residue sequence is between the start and end sequence."""
        # residue - between start and end
        hetatm_flag, resseq, icode = residue.get_id()
        # if hetatm_flag != " ":
        #     # skip HETATMS
        #     return 0
        if icode != " ":
            warnings.warn(
                f"WARNING: Icode {icode} at position {resseq}", BiopythonWarning
            )
        if self.start <= resseq <= self.end:
            return 1
        return 0

Dice.ChainSelector.accept_residue = accept_residue_mod


i = 1

for res in ligands_map_pdb_red :
    
    print(struct, res[2], int(res[1]), int(res[1]))

    Dice.extract(struct, res[2], int(res[1]), int(res[1]), f"lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.pdb")
    
    
    try:
        subprocess.run(["openbabel.obabel", "-ipdb", f"lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.pdb", "-osmi" ,"-O",
                                           
        f"lig_{res[3]}_{res[2]}_{str(i).zfill(5)}.smi"])
                            
        
    except Exception as excp:
                            
        print('\n ', excp, '\n')
                            
                            
                        
    i += 1

```


---

