voevent-parse
#¶from __future__ import print_function
import voeventparse as vp
IPython Tip #1: In IPython (terminal or notebook) you can quickly check the docstring for something by putting a question mark in front, e.g.
# Uncomment the following and hit enter:
# ?vp.load
Alternatively, you can always read the docs, which include autogenerated API specs.
Ok, let's load up a voevent (click here to see the raw XML):
with open('voevent.xml') as f:
v = vp.load(f)
IPython Tip #2: We also get tab-completion. Simply start typing the name of a function (or even just the '.' operator) and hit tab to see valid possible options - this is handy for exploring VOEvent packets:
# Uncomment the following and hit tab:
# v.
v.Who.Date.text
'1970-01-01T00:00:00'
print("Inferred reason is", v.Why.Inference.Name.text)
print( "(A string of length {})".format(len(v.Why.Inference.Name.text)))
type(v.Why.Inference.Name.text)
Inferred reason is GRB121212A (A string of length 10)
str
XML Tip #2: Note that there are two ways to store data in an XML packet:
attrib
, which behaves like a Python dictionary, e.g.:print(v.attrib['ivorn'])
print(v.attrib['role'])
ivo://example/exciting_events#123 test
v.Why.Inference.attrib
{'relation': 'identified', 'probability': '0.1'}
So far, each of the elements we've accessed has been the only one of that name - i.e. our VOEvent has only one Who
child-element, likewise there's only one Inference
under the Why
entry in this particular packet. But that's not always the case; for example the What
section contains a Group
with two child-elements called Param
:
print(vp.prettystr(v.What.Group))
<Group xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:voe="http://www.ivoa.net/xml/VOEvent/v2.0" name="source_flux"> <Param dataType="float" name="peak_flux" ucd="em.radio.100-200MHz" unit="Janskys" value="0.0015"> <Description>Peak Flux</Description> </Param> <Param dataType="float" name="int_flux" ucd="em.radio.100-200MHz" unit="Janskys" value="2.0e-3"> <Description>Integrated Flux</Description> </Param> </Group>
So how do we access all of these? This is where we start getting into the details of lxml.objectify syntax (which voevent-parse uses under the hood). lxml.objectify uses a neat, but occasionally confusing, trick: when we access a child-element by name, what's returned behaves like a list:
v.What[0] # v.What behaves like a list!
<Element What at 0x7f6cda833c20>
However, to save having to type something like v.foo[0].bar[0].baz[0]
, the first element of the list can also be accessed without the [0]
operator (aka 'syntactic sugar'):
v.What is v.What[0]
True
Knowing that it's 'just a list', we have a couple of options, we can iterate:
for par in v.What.Group.Param:
print(par.Description)
Peak Flux Integrated Flux
Or we can check the length, access elements by index, etc:
len(v.What.Group.Param)
2
v.What.Group.Param[1].Description
'Integrated Flux'
Note that another example of this 'syntactic sugar' is that we can display the text-value of an element without adding the .text
suffix.
However, see below for why it's a good idea to always use .text
when you really do want the text-value of an element:
print(v.Why.Inference.Name) # More syntax sugar - if it has a string-value but no children, print the string
print(v.Why.Inference.Name.text) # The safe option
print(v.Why.Inference.Name.text[:3]) # Indexing on the string as you'd expect
print(v.Why.Inference.Name[:3]) # This is indexing on the *list of elements*, not the string!
GRB121212A GRB121212A GRB ['GRB121212A']
If that all sounds awfully messy, help is at hand: you're most likely to encounter sibling elements under the What
entry of a VOEvent, and voevent-parse has a function to convert that to a nested Python dictionary for you:
# Consult the docstring
#?vp.pull_params
what_dict = vp.pull_params(v)
what_dict
{None: {}, 'source_flux': {'int_flux': {'dataType': 'float', 'value': '2.0e-3', 'ucd': 'em.radio.100-200MHz', 'name': 'int_flux', 'unit': 'Janskys'}, 'peak_flux': {'dataType': 'float', 'value': '0.0015', 'ucd': 'em.radio.100-200MHz', 'name': 'peak_flux', 'unit': 'Janskys'}}}
what_dict['source_flux']['peak_flux']['value']
'0.0015'
Since voevent-parse uses lxml.objectify, the full power of the LXML library is available when handling VOEvents loaded with voevent-parse.
We already saw how you can access a group of child-elements by name, in list-like fashion. But you can also iterate over all the children of an element, even if you don't know the names ('tags', in XML-speak) ahead of time:
for child in v.Who.iterchildren():
print(child.tag, child.text, child.attrib)
AuthorIVORN ivo://hotwired.org {} Date 1970-01-01T00:00:00 {} Author None {}
for child in v.WhereWhen.ObsDataLocation.ObservationLocation.iterchildren():
print(child.tag, child.text, child.attrib)
AstroCoordSystem None {'id': 'UTC-FK5-GEO'} AstroCoords None {'coord_system_id': 'UTC-FK5-GEO'}
Another powerful technique is to find elements using Xpath or ElementPath queries, but this is beyond the scope of this tutorial: we leave you with just a single example:
v.find(".//Param[@name='int_flux']").attrib['value']
'2.0e-3'
Congratulations! You should now be able to extract data from just about any VOEvent packet. Note that voevent-parse comes with a few convenience routines to help with common, tedious operations, but you can always compose your own.
If you put together something that you think others could use (or find a bug!), pull requests are welcome.
Next stop: authoring your own VOEvent.