Wiki

Case Status Kiln
Register Log In

Wiki

 
Detailed Guide to Using FogBug…
  • RSS Feed

Last modified on 11/4/2016 11:25 AM by User.

Tags:

Detailed Guide to Using FogBugzPy

What is FogBugzPy?

FogBugzPy is a Python module that makes use of Beautiful Soup, a parsing library that makes it very easy to work with HTML and XML, to interact with the FogBugz XML API. We'll show you how to use Beautiful Soup insofar as it applies to using FogBugzPy, but if you're looking for more, you can read the Beautiful Soup documentation.

For instructions on installing FogBugzPy, see Installing Python and FogBugzPy

The source code for FogBugzPy is available at https://developers.kilnhg.com/Repo/FogBugz/Group/FogBugzPy

Conventions Used In This Guide

  • fbSettings.py - These examples assume you have set up a fbSettings.py file. See Storing Configuration Settings in fbSettings.py.
  • Executing sample code - each bit of sample code can be copied into file called example.py that sits next to fbSettings.py. Once the code is copied, you can save example.py and execute it by typing the following from the command line:

    1:
    
    > python example.py

FogBugzPy and XML API Version

As of FogBugzPy version 1.0.6, you can specify an expected API version when you initialize the FogBugz object. If a backwards-incompatible change is ever made to the FogBugz API and your script specifies an earlier API version, FogBugzPy will raise an exception and alert you to the need to update your script. When there are backwards-compatible updates to the API, FogBugzPy will print a message to the console letting you know of the option to upgrade but will not throw an exception.

1:
2:
3:
4:
5:
6:
>>> from fogbugz import FogBugz
>>> import fbSettings

>>> fb = FogBugz(fbSettings.URL, fbSettings.TOKEN, api_version=6)
There is a newer version of the FogBugz API available. Please update to version 8 to avoid errors in the future
>>>

FogBugzPy and XML API Documentation

The FogBugz XML API Documentation is written in a way that is language and framework agnostic. This means that any language that can parse XML within a framework that can communicate over HTTP can use the XML API. However, without a frame of reference, getting started with the API can introduce a higher barrier to entry than if the API were written targeting a single language/framework. The FogBugzPy library and this development wiki seek to lower the barrier to entry to working with the FogBugz XML API.

When using FogBugzPy with Documentation, a cmd in the documentation becomes a method a FogBugz object in Python. Likewise,  Arguments in the documentation become parameters to methods in Python. Let's look at an example.

The following is a clip of the Editing Cases section of the documentation:

We want to edit an existing FogBugz case, changing its title, so this is how we write that code in FogBugzPy:

1:
2:
3:
4:
5:
6:
7:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

fb.edit(ixBug=1,                      #fb.edit corresponds to cmd=edit
        sTitle="This is a new title") #ixBug and sTitle are both arguments

Notice how the edit method in the Python script corresponds to cmd=edit in the documentation. ixBug and sTitle, which are arguments in the documentation, become method parameters in Python.

XML API Responses

When the FogBugz XML API returns a response, it returns XML.

Viewing the XML Response

For example, here's a sample script that stores the results of a search command and stores it in a variable called resp. It then prints the resp.

1:
2:
3:
4:
5:
6:
7:
8:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q=1,cols="ixBug,sTitle") #store the results of the search in a
                                          #variable called resp
print resp  #print resp to see what it looks like

When we execute, this script, we get:

1:
2:
> python example.py
<response><cases count="1"><case ixBug="1" operations="edit,assign,resolve,email,remind"><ixBug>1</ixBug><sTitle><![CDATA[This is a new title]]></sTitle></case></cases></response>

That's fairly difficult to read. Let's use the prettify() method to make the output readable:

1:
2:
3:
4:
5:
6:
7:
8:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q=1,cols="ixBug,sTitle") #store the results of the search in a
                                          #variable called resp
print resp.prettify()  #print resp using prettify() to see what it looks like

Now when we execute the script, it's much easier to read:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
> python example.py
<response>
<cases count="1">
 <case ixBug="1" operations="edit,assign,resolve,email,remind">
  <ixBug>
   1
  </ixBug>
  <sTitle>
   <![CDATA[This is a new title]]>
  </sTitle>
 </case>
</cases>
</response>

IMPORTANT! - In older versions of FogBugzPy, elements and columns are always lower case. Even though columns and attributes are camel cased (e.g. ixBug) in the Documentation, earlier versions of Beautiful Soup convert all elements to lower case. Thus, even though you will request ixBug, the response will always need to be accessed in lowercase, e.g. ixbug. As of FogBugzPy v.1.0.5, this is no longer the case; elements and columns appear in camelCase in the response.

Extracting Data From XML Elements

When you get a response back, you usually want to do something with that data. With Beautiful Soup, when you want to parse XML data, each child element becomes a Python attribute of the parent object. In the XML output above, we can see that response is a parent of cases, which is a parent of case, which is a parent of sTitle, so if we want to get the sTitle value the response, we could use resp.cases.case.sTitle. Let's print it and see what we get:

1:
2:
3:
4:
5:
6:
7:
8:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q=1,cols="ixBug,sTitle") #store the results of the search in a
                                          #variable called resp
print resp.cases.case.sTitle

When executed:

1:
2:
> python example.py
<sTitle><![CDATA[This is a new title]]></sTitle>

That returns the whole element, which isn't perhaps what we wanted. If we want just the text inside the sTitle element without the element itself, we can add .string to our code:

1:
2:
3:
4:
5:
6:
7:
8:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q=1,cols="ixBug,sTitle") #store the results of the search in a
                                          #variable called resp
print resp.cases.case.sTitle.string       #adding .string gives us just text

Now, when executed:

1:
2:
> python example.py
This is a new title

For nearly all operations, you can use case.columnName.string to get the data you want. We'll explore how to work with various data types in an upcoming section.

Extracting Data From XML Attributes

In the example above, we looked at extracting data from the <sTitle>...</sTitle> element. What if we want to get the value associated with the ixBug value of case, e.g. <case ixBug="1">? We can treat the tag (element) object as if it were a dictionary, e.g. case['ixBug']. The following example shows the difference between accessing the text within a tag versus accessing its attribute:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q=1,cols="ixBug,stitle")

print resp.cases.case.sTitle.string # This prints the text within the <sTitle> element
print resp.cases.case['ixBug']      # This prints the ixBug attribute of the <case>
                                    # element, e.g. <case ixBug="1">

Working with Data Types

You can use the syntax we described above for extracting data from XML Elements, e.g. case.columnName.string, for most of your needs. However, this will always return some form of string value. Python will try to infer types when it can, but sometimes you need to explicitly work with other data types.

Integers

Use int(expression) to explicitly convert to an integer, e.g. int(resp.cases.case.ixBug.string). Note how we still have to use .string with the ixBug element.

Strings

Although the value for each element is a string, text elements (such as sTitle or sEvent) will be wrapped in CDATA by the XML API. FogBugzPy v.1.0.5 handles CDATA automatically and these fields can be treated like regular strings; however, this is a bit of annoyance in older versions of FogBugzPy. You can work around this issue by encoding your strings as UTF-8, using .encode('UTF-8'). In the following example, look at the difference in string replacement when you lack encoding versus including it:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q=1,cols="ixBug,sTitle")

bug = resp.cases.case.ixbug.string
sTitle = resp.cases.case.stitle.string
print "%s: %s" % (bug, sTitle) #look at how stitle prints, wrapped in CDATA
# 1: <![CDATA[This is a new title]]>

sTitle = resp.cases.case.stitle.text #returns the text contents of the field
print "%s: %s" % (bug, sTitle) #this time, CDATA is not present
# 1: This is a new title

As executed:

1:
2:
3:
> python example.py
1: <![CDATA[This is a new title]]>
1: This is a new title

Floating-point numbers

You can convert to a floating-point number using either float(expression) or Decimal(expression). In most cases, float is fine. In the following example, we've added an Estimate: Current to Case 1 so that we can use hrsCurrEst via the XML API.

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
from fogbugz import FogBugz
import fbSettings
from decimal import Decimal #you'll need to import Decimal if you want to use it

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q=1,cols="ixBug,sTitle,hrsCurrEst")

dblHrsCurrEst = float(resp.cases.case.hrsCurrEst.string)  #convert to a float
decHrsCurrEst = Decimal(resp.cases.case.hrsCurrEst.string)#convert to a decimal

print dblHrsCurrEst
print decHrsCurrEst

As executed:

1:
2:
3:
> python example.py
3.4
3.4

Date and Time Values

All Date and Time values returned from FogBugz are provided as UTC. You'll need to use the datetime module with the strptime method with the format '%Y-%m-%dT%H:%M:%SZ' to convert from strings to datetime values. In the following example, we'll show how to work with datetime and timedelta objects:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
from fogbugz import FogBugz
import fbSettings
from datetime import datetime, timedelta #these are needed to work with date/time values

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q=1,cols="ixBug,sTitle,dtOpened")

#use datetime.strptime(element.string, '%Y-%m-%dT%H:%M:%SZ') to convert to a datetime
dtOpenedUtc = datetime.strptime(resp.cases.case.dtOpened.string,'%Y-%m-%dT%H:%M:%SZ')

print "This is what case.dtOpened.string looks like in XML: %s" % resp.cases.case.dtOpened.string
print "We can see that dtOpenedUtc is a datetime type: %s" % type(dtOpenedUtc)
print "This is what dtOpenedUtc looks like printed: %s" % dtOpenedUtc

year = dtOpenedUtc.year
print "dtOpenedUtc.year tells us the year: %d" % year

#this will make a time delta object so we can find the difference in times
tdSinceToday = datetime.utcnow() - dtOpenedUtc
days = tdSinceToday.days
print "This case was opened %d days ago" % days

As executed:

1:
2:
3:
4:
5:
6:
PS C:\code\Faciliscript> python example.py
This is what case.dtOpened.string looks like in XML: 2011-02-17T14:47:07Z
We can see that dtOpenedUtc is a datetime type: <type 'datetime.datetime'>
This is what dtOpenedUtc looks like printed: 2011-02-17 14:47:07
dtOpenedUtc.year tells us the year: 2011
This case was opened 223 days ago

Boolean values

Boolean values are returned as strings, true or false, with an (for flag) prefixing the variable name. Test to see if a variable is True or False by comparing to the string values 'true' or 'false':

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q=1,cols="ixBug,sTitle,fOpen")

if resp.cases.case.fOpen.string == 'true':
    print "This case is open."
else:
    print "This case is closed."

As executed:

1:
2:
PS C:\code\misc> python .\example.py
This case is open.

NOTE: On some commands (e.g. listPeople), there are boolean arguments that require a 1 (for true) or 0 (for false).

Looping Over Collections

Looping over multiple elements in a collection is easy. With Beautiful Soup, we can use either elements.childGenerator() or elements.findAll('element') to generate a collection. From there, we simply use Python's for item in collection: syntax.

Here's an example that loops over a list of projects using elements.childGenerator():

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.listProjects()

for project in resp.projects.childGenerator():
    print "%s: %s" % (project.ixProject.string, 
                      project.sProject.string.encode('UTF-8'))

As executed:

1:
2:
3:
4:
> python .\example.py
1: Inbox
2: FogBugz
3: Kiln

Here's another example that loops over a list of cases using for item in collection::

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
from fogbugz import FogBugz
import fbSettings

fb = FogBugz(fbSettings.URL, fbSettings.TOKEN)

resp = fb.search(q='assignedTo:"me" status:"Active"',
                 cols="ixBug,sTitle",
                 max=10)

for case in resp.cases.findAll('case'):
    print "%s: %s" % (case.ixBug.string, 
                      case.sTitle.string)

As executed:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
> python .\example.py
1: This is a new title
152: "Welcome to FogBugz" Sample Case
151: test
150: (Untitled)
149: test
147: I would like the program to say Hello Earth instead
146: Adding a reminder!
26: (Untitled)
3: Project: Person Client: Hello This is for the other one
145: Test