The parsed data model

After parse(), everything pidibble extracted lives in one attribute:

>>> from pidibble.pdbparse import PDBParser
>>> p = PDBParser(source_db='rcsb', source_id='4zmj').parse()
>>> p.parsed
{...}

parsed is a PDBRecordDict — a dictionary keyed by record type.

Single records versus record lists

Each value is one of two things:

  • a single PDBRecord, for records that occur once per structure (HEADER, TITLE, CRYST1, …); or

  • a PDBRecordList of PDBRecord instances, for multiple-entry records that recur (ATOM, SEQRES, LINK, …).

PDBRecordList subclasses collections.UserList, so test for it with isinstance — a plain type(v) == list check never matches, because a UserList is not a list:

>>> from pidibble.pdbrecord import PDBRecordList
>>> isinstance(p.parsed['ATOM'], PDBRecordList)      # a multiple-entry record
True
>>> isinstance(p.parsed['HEADER'], PDBRecordList)    # a single-instance record
False
>>> [k for k, v in p.parsed.items() if type(v) == list]   # the naive check never matches
[]

For everyday use, a PDBRecordList behaves like a list — index it, slice it, iterate it, take its len:

>>> atoms = p.parsed['ATOM']
>>> len(atoms)
4518
>>> first, last = atoms[0], atoms[-1]

Which records land in which bucket is documented per record type in Supported record types.

Records and their fields

A PDBRecord stores each parsed field as an instance attribute of the appropriate Python type — strings, int, float, or nested record objects. The quickest way to see what a record contains is pstr() (“pretty string”):

>>> print(p.parsed['HEADER'].pstr())
HEADER
      classification: VIRAL PROTEIN
             depDate: 04-MAY-15
              idCode: 4ZMJ

The keys shown by pstr() are exactly the attribute names:

>>> h = p.parsed['HEADER']
>>> h.classification, h.depDate, h.idCode
('VIRAL PROTEIN', '04-MAY-15', '4ZMJ')

pstr() takes an excludes list (bookkeeping fields key, format and continuation are hidden by default) and a pad width for the labels, so you can widen or narrow the display or reveal the hidden internals.

Residues and other structured sub-fields

Some fields are not scalars but small structured objects. The most common is a residue, which groups a residue name, chain id, sequence number and insertion code:

>>> a = p.parsed['ATOM'][0]
>>> print(a.residue)
resName: LEU; chainID: G; seqNum: 34; iCode:
>>> a.residue.resName, a.residue.chainID, a.residue.seqNum
('LEU', 'G', 34)

Records that describe a relationship between residues expose more than one:

>>> b = p.parsed['SSBOND'][0]
>>> b.residue1.resName, b.residue1.chainID, b.residue1.seqNum
('CYS', 'G', 54)
>>> b.residue2.resName, b.residue2.chainID, b.residue2.seqNum
('CYS', 'G', 74)

Working with the model as a whole

Because parsed is an ordinary mapping, the usual idioms apply — membership tests, iteration, comprehensions:

>>> 'ATOM' in p.parsed and 'HETATM' in p.parsed
True
>>> n_hetatm = len(p.parsed['HETATM'])
>>> chains = {a.residue.chainID for a in p.parsed['ATOM']}
>>> sorted(chains)
['B', 'G']

A record type that was not present in the file is simply absent from parsed, so guard with in (or dict.get) before reaching for an optional record:

>>> 'SPLIT' in p.parsed
False