lxml objectify and Working with XML Tag Containing Periods

Recently, I have been working on automating Jenkin’s config XML files using Python and the lxml package.

I have several articles on using lxml and its handy objectify functionality:

But I’ve never really covered what to do when you need to create or parse an XML tag that contains periods.

For example, Jenkin’s config files are notorious for having tags named like this: <hudson.triggers.TimerTrigger>

The way to get the value of a tag that contains periods is to use lxml’s objectify’s __getattr__() method and pass in the strangely named tag name:

For example:

from lxml import objectify

with open("config_no_build_trigger.xml") as xml_file:
    config = xml_file.read()

root = objectify.fromstring(config.encode())

timer_trigger = root.triggers.__getattr__("hudson.triggers.TimerTrigger")

But what do you do if you need to create a tag that contains periods in its name?

I wanted to enable the Build Periodically function in Jenkins programmatically. There are several tags and sub-tags that contain periods that I would need to create if I wanted to turn this feature on in a Jenkins job.

To do it successfully, you need to use __setattr__() !

Here’s an example:

from lxml import etree, objectify

with open("config_no_build_trigger.xml") as xml_file:
    config = xml_file.read()

root = objectify.fromstring(config.encode())

try:
    _ = root.properties.__getattr__(
        "org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty"
    )
except AttributeError:
    # Add org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty
    # XML element
    root.properties.__setattr__("org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty", "")

try:
    trigger_prop = root.properties.__getattr__(
        "org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty"
    )
    _ = trigger_prop.triggers
except AttributeError:
    # Add trigger XML element
    trigger_prop = root.properties.__getattr__(
        "org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty"
    )
    _ = trigger_prop.triggers = objectify.Element("triggers")

try:
    trigger_prop = root.properties.__getattr__(
        "org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty"
    )
    _ = trigger_prop.triggers.__getattr__("hudson.triggers.TimerTrigger")
except AttributeError:
    # Add hudson.triggers.TimerTrigger XML element
    trigger_prop = root.properties.__getattr__(
        "org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty"
    )
    _ = trigger_prop.triggers.__setattr__("hudson.triggers.TimerTrigger", "")

try:
    trigger = root.properties.__getattr__(
        "org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty"
    )
    trigger.triggers.__getattr__(
        "hudson.triggers.TimerTrigger"
    ).spec
except AttributeError:
    # Add spec XML element
    trigger = root.properties.__getattr__(
        "org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty"
    )
    trigger.triggers.__getattr__(
        "hudson.triggers.TimerTrigger"
    ).spec = objectify.Element("spec")

# Modify the schedule
try:
    trigger = root.properties.__getattr__(
        "org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty"
    )
    trigger.triggers.__getattr__(
        "hudson.triggers.TimerTrigger"
    ).spec = "H 18 * * *"
except AttributeError:
    print(f"ERROR: Unable to add schedule!")

objectify.deannotate(root)
etree.cleanup_namespaces(root)

config_xml = etree.tostring(root, pretty_print=True, encoding=str)
with open("new_config.xml", "w") as xml_file:
    xml_file.write(config_xml)

This example demonstrates how to use Python exception handling to create all the XML tags and sub-tags effectively and how to set the new tag to a value.

Hopefully, you have found this mini-tutorial helpful and will be able to use it yourself someday!