This was bothering me probably more than it should have, and after getting to know python a little bit I've found that the problem is pretty easy to solve. Since I'm mostly an R user, these lines of python code might not be very pythonic. Feel free to comment / edit:
EDIT: Since the whole script is fairly long I've added everything to a Github Repo: https://github.com/ratnanil/PythonToolboxDocumentation
Step 1: Adding attributes within .pyt File
Add the documentation to the .pyt File as attributes to the objects. For example, adding adding a "Dialog Reference" for an individual Parameter can be created like so:
param0 = arcpy.Parameter(
displayName="Input workspace",
name="in_workspace",
datatype="DEWorkspace",
parameterType="Required",
direction="Input")
param0.dialogref = "Enter explanatory information about this tool here"
You are free to choose your own names for the attributes (eg dialogref
), just make sure you are not overwriting existing names (name
, direction
, value
etc.).
Step 2: Create a python Script to extract the tool's documentation and write xml Files
This file will hold the code to extract all the documentation from the .pyt File and write the xml files based on this information.
import shutil
import importlib
import os
import datetime
import xml.etree.cElementTree as ET
# enter the filename of pyt-File WITHOUT extension
pythontoolbox = "mytoolbox"
# this function copies the .pyt- to a .py-file (and deletes a previous version if it exists). We need this because I have not found a way to directly import a .pyt-file
def copypyt(pythontoolbox):
pyfile = pythontoolbox+".py"
pytfile = pythontoolbox + ".pyt"
if os.path.isfile(pyfile):
os.remove(pyfile)
shutil.copy(pytfile, pyfile)
# run the above function to create a .py Copy of the .pyt-File
copypyt(pythontoolbox)
# imports the toolbox based on the "pythontoolbox"-Variable. The module is first stored in a variable for later use
import_toolbox = importlib.import_module(pythontoolbox)
import_toolbox
# since the module is stored in a variable, we can reload it in case the pyt file experienced changes during the session
importlib.reload(import_toolbox)
# import all the tools from the toolbox
toolbox_tools = import_toolbox.Toolbox().tools
# we could also import the toolbox label and alias, but we don't need in this minimal example
# toolbox_label = import_toolbox.Toolbox().label
# toolbox_alias = import_toolbox.Toolbox().alias
# Get today's date
today = datetime.datetime.now().strftime("%Y%m%d")
# the following for-loop creates an .xml file for each tool, including all metadata extracted from the pyt file
for tool_i in toolbox_tools:
# creates the first level tag named "metadata"
metadata = ET.Element("metadata")
# creates a sublevel tag named "esri"
esri = ET.SubElement(metadata, "Esri")
# Add todays date to the xml Tag "CreaDate", but maybe the creation date of pyt would be more appropriate
ET.SubElement(esri, "CreaDate").text = today
# I don't care too much about some of the elements in the xml file. In this example, I've included them none the less but filled with some dummy data. these are marked with a # *
ET.SubElement(esri, "CreaTime").text = today # *
ET.SubElement(esri, "ArcGISFormat").text = "1.0" # *
ET.SubElement(esri, "SyncOnce").text = "TRUE" # *
ET.SubElement(esri, "ModDate").text = today # *
ET.SubElement(esri, "ModTime").text = today # *
scaleRange = ET.SubElement(esri, "scaleRange")
ET.SubElement(scaleRange, "minScale").text = "150000000" # *
ET.SubElement(scaleRange, "maxScale").text = "5000"
tool_name = tool_i.__name__
tool_fullname = str(tool_i).replace("<class '", "").replace("'>", "")
tool_label = tool_i().label
tool_description = tool_i().description
tool_canRunInBackground = tool_i().canRunInBackground = False
tool_category = tool_i().category
tool = ET.SubElement(metadata,
"tool",
xmlns="",
name=tool_name,
displayname=tool_label,
toolboxalias="")
ET.SubElement(tool,"arcToolboxHelpPath").text = "c:\\program files\\arcgis\\pro\\Resources\\Help\\gp" # *
parameters = ET.SubElement(tool, "parameters")
# this nested loop writes the documentation for all parameters
for param_i in tool_i().parameters:
param_displayName = param_i.displayName
param_name = param_i.name
param_datatype = param_i.datatype
param_parameterType = param_i.parameterType
param_direction = param_i.direction
param_value = param_i.value
# For all custom parameters, include a failsafe in case some attributes were note defined for some tools
hasattr(toolbox_tools[1]().parameters[1],"pythonref")
if hasattr(param_i, "dialogref"):
dialogref = param_i.dialogref
else:
dialogref = ""
if hasattr(param_i, "pythonref"):
pythonref = param_i.pythonref
else:
pythonref = ""
param = ET.SubElement(parameters,
"param",
name=param_name,
displayname=param_displayName,
type=param_parameterType,
direction=param_direction,
datatype=param_datatype,
expression=param_name)
reference_style_before = "<DIV STYLE=\"text-align:Left;\"><DIV><P><SPAN>"
reference_style_after= "</SPAN></P></DIV></DIV>"
dialogReference = ET.SubElement(param, "dialogReference").text = reference_style_before + dialogref + reference_style_after
pythonReference = ET.SubElement(param, "pythonReference").text = reference_style_before + pythonref + reference_style_after
summary = ET.SubElement(tool,"summary").text = reference_style_before + tool_description + reference_style_after
usage = ET.SubElement(tool,"usage").text = reference_style_before + "usage" + reference_style_after
scriptExamples = ET.SubElement(tool,"scriptExamples")
scriptExample = ET.SubElement(scriptExamples,"scriptExample")
ET.SubElement(scriptExample,"title").text = "title for eg 1"
ET.SubElement(scriptExample,"para").text = "some description for code sample 1"
ET.SubElement(scriptExample,"code").text = "code sample 1"
dataIdInfo = ET.SubElement(metadata, "dataIdInfo")
idCitation = ET.SubElement(dataIdInfo, "idCitation")
resTitle = ET.SubElement(idCitation, "resTitle").text = tool_label
idCredit = ET.SubElement(dataIdInfo, "idCredit").text = "Credit whom credit is due" # *
searchKeys = ET.SubElement(dataIdInfo, "searchKeys")
ET.SubElement(searchKeys, "keyword").text = "tag1, tag2, tag3" # *
distInfo = ET.SubElement(metadata, "distInfo") # *
distributor = ET.SubElement(distInfo, "distributor") # *
distorFormat = ET.SubElement(distributor, "distorFormat") # *
ET.SubElement(distorFormat, "formatName").text = "ArcToolbox Tool"
mdHrLv = ET.SubElement(metadata, "mdHrLv")
ET.SubElement(mdHrLv, "ScopeCd ", value = "005") # *
mdDateSt = ET.SubElement(metadata, "mdDateSt",Sync="TRUE").text = today # *
tree = ET.ElementTree(metadata)
tree.write(tool_fullname+".pyt.xml")
Step 3: Create a python Script to extract the toolbox's documentation and write xml Files
Now you have created the most important part of the documentation (for each tool). In a similar manner, you can now create the documentation for the toolbox itself.