[PYTHON] Automatically generate Object specifications with Blue Prism

Introduction

In Blue Prism, the parts that actually operate the application called Object and the parts that write the business logic called Process are separated, and it is a mechanism to improve reusability. In order to promote the reuse of Objects within the team, it is necessary to create and maintain a document "What kind of Object is there", but it is difficult to create and maintain by hand, and the contents of the Object have been updated. There may be problems such as not being able to follow.

This article deals with the story of automatically generating documentation for such important Objects (ODI: Object Design Instruction in Blue Prism terminology).

There is a VBO called "Object Inventory"! Does not work. .. ..

I have a VBO called "Object Inventory" in Ditigal Exchange (https://digitalexchange.blueprism.com/dx/entry/3439/solution/object-inventory), but the string trim is supposed to be in English. Or it didn't work because I got a mysterious Excel error (Exception: Exception from HRESULT: 0x800A03EC). .. ..

However, there was the following description on the help page.

This asset is a business object that uses the output from the BP command line /getbod function to create a list of all business objects, pages, descriptions, inputs and outputs.



 The fact that AutomateC.exe has a switch called ``` / getbod``` means that the Help Command Line Options (https://bpdocs.blueprism.com/bp-6-8/ja-jp/helpCommandLine. It is not written in htm) either. .. ..

# Switches called / listprocesses and / getbod
 Looking into the "Object Inventory" VBO above, it seems that the ``` / listprocesses``` switch gets a list of Processes and Objects and calls` `/ getbod``` for each. If the target is Process, the string `` `Could not find Business Object` `` will be returned, so it seems that they are excluded from the processing target.

```/getbod```The switch really existed!

# Try to implement with python

 It's a poor script, but it works. We hope for your reference. (Verified with Python 3.7.4)

## For listprocesses, getbod ones are saved in a text file.

```python
"""
BP parameters for connecting to Blue Prism_USERNAME, BP_PASSWORD, BP_Set it in the DBCONNAME environment variable and execute.

BP_USERNAME :username
BP_PASSWORD :password
BP_DBCONNAME :Connection name
"""
import delegator
from pathlib import Path
import logging
import os

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

bp_username = os.environ["BP_USERNAME"]
bp_password = os.environ["BP_PASSWORD"]
bp_dbconname = os.environ["BP_DBCONNAME"]

LISTPROCESSES_CMD = '"C:\Program Files\Blue Prism Limited\Blue Prism Automate\AutomateC.exe" /user {bp_username} {bp_password} /dbconname {bp_dbconname} /listprocesses'
command = LISTPROCESSES_CMD.format(
    bp_username=bp_username, bp_password=bp_password, bp_dbconname=bp_dbconname
)
context = delegator.run(command)
object_list = context.out
object_names = object_list.splitlines()
logger.info(object_names)


GETBOD_CMD = '"C:\Program Files\Blue Prism Limited\Blue Prism Automate\AutomateC.exe" /user {bp_username} {bp_password} /dbconname {bp_dbconname} /getbod "{object_name}"'

for object_name in object_names:
    command = GETBOD_CMD.format(
        bp_username=bp_username,
        bp_password=bp_password,
        bp_dbconname=bp_dbconname,
        object_name=object_name,
    )
    context = delegator.run(command)
    description = context.out
    if (
        len(description.splitlines()) <= 1
    ):  #Process is explained"The business object XX was not found"Only one line is output
        logger.info("{} is not a object".format(object_name))
        continue
    #If slash is included in the file name, the file cannot be created, so replace it.
    description_file_name = object_name.replace("/", "_") + ".txt"
    with open(Path("output_descriptions") / description_file_name, "w") as f:
        f.write(context.out)

Save the getbod text file as markdown

Please let me know if there is a library that can be parsed more easily. .. ..

from typing import List, Optional
from dataclasses import dataclass, field
import re
import enum
import logging
from pathlib import Path

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)


@dataclass
class VBOActionDescription:
    """
A class that holds information about each action in the VBO
    """

    action_name: str
    description: str = ""
    pre_condition: str = ""
    post_condition: str = ""
    input_params: List[str] = field(default_factory=list)
    output_params: List[str] = field(default_factory=list)

    def as_markdown(self) -> str:
        """
Express in Markdown format
        """
        _md = (
            "###{action_name}\n"
            "{description}\n"
            "\n"
            "####Pre-conditions\n"
            "{pre_condition}\n"
            "\n"
            "####Post-conditions\n"
            "{post_condition}\n"
            "\n"
            "####Input parameters\n"
            "{input_params}\n"
            "\n"
            "####Output parameters\n"
            "{output_params}\n"
            "\n"
        )
        input_params_md = ("\n").join(
            ["* {}".format(input_param) for input_param in self.input_params]
        )
        output_params_md = ("\n").join(
            ["* {}".format(output_param) for output_param in self.output_params]
        )

        out = _md.format(
            action_name=self.action_name,
            description=self.description,
            pre_condition=self.pre_condition,
            post_condition=self.post_condition,
            input_params=input_params_md,
            output_params=output_params_md,
        )
        return out


class VBODescription:
    """
A class that holds VBO information. Have a small list of VBOActionDescription
    """

    def __init__(
        self,
        object_name: str,
        description: Optional[str] = "",
        mode: str = "",
        actions: Optional[List[VBOActionDescription]] = None,
    ):
        self.object_name = object_name
        self.description = description
        self.mode = mode
        self.actions = actions

    def as_markdown(self) -> str:
        """
Express in Markdown format
        """
        _md = (
            "#{object_name}\n"
            "{description}\n"
            "\n"
            "##action mode\n"
            "{mode}\n"
            "\n"
            "##action\n"
        )

        out = _md.format(
            object_name=self.object_name, description=self.description, mode=self.mode
        )
        if self.actions:
            out = out + ("\n").join([action.as_markdown() for action in self.actions])

        return out


class DescriptionOfWhat(enum.Enum):
    """
When parsing the description of VBO, I needed the information "Which part are you reading?"
    """

    business_object = "Business Object"
    action = "Action"
    pre_condition = "Pre Conditiion"
    post_condition = "Post Conditiion"


def classify_line(line: str):
    """
Classify lines
    """
    line = line.strip()
    # =Business object- Utility - File Management=  <=Match the line like the one on the left
    match = re.search("^=(?P<content>[^=]*)=$", line)
    if match:
        return {"type": "object name", "content": match.groupdict()["content"]}
    #The execution mode of this business object is "background"<=Match the line like the one on the left
    match = re.search("^The execution mode of this business object is "(?P<mode>[^」]+)"is", line)
    if match:
        return {"type": "mode", "content": match.groupdict()["mode"]}
    # ==Append to Text File==  <=Match the line like the one on the left
    match = re.search("^==(?P<content>[^=]*)==$", line)
    if match:
        return {"type": "action name", "content": match.groupdict()["content"]}
    # ===Prerequisites===  <=Match the line like the one on the left
    match = re.search("^===(?P<content>[^=]*)===$", line)
    if match:
        content = match.groupdict()["content"]
        if content == "Prerequisites":  #The translation on the Blue Prism side is strange. .. ..
            return {"type": "pre condition", "content": content}
        if content == "end point":  #The translation on the Blue Prism side is strange. .. ..
            return {"type": "post condition", "content": content}
        #other than that
        return {"type": "action attribute", "content": content}
    # *input:File Path (text) - Full path to the file to get the file size   <=Match the line like the one on the left
    match = re.search("^\*input:(?P<content>.*)$", line)
    if match:
        return {"type": "input parameter", "content": match.groupdict()["content"]}
    # *output:File Path (text) - Full path to the file to get the file size  <=Match the line like the one on the left
    match = re.search("^\*output:(?P<content>.*)$", line)
    if match:
        return {"type": "output parameter", "content": match.groupdict()["content"]}
    #Other lines
    return {"type": "article", "content": line}


def append_action_to_vbo_description(latest_action, vbo_description):
    actions = vbo_description.actions
    if actions:
        vbo_description.actions.append(latest_action)
    else:
        vbo_description.actions = [
            latest_action,
        ]
    return vbo_description


def convert_to_markdown(bod_description_filepath) -> str:
    """
The body of the process to convert to Markdown
    """
    vbo_description = None
    with open(bod_description_filepath, "r", encoding="shift_jis", newline="\r\n") as f:
        previous_line_type: Optional[DescriptionOfWhat] = None  #Keep track of what you are reading.
        latest_action = None

        for line in f:
            line_class = classify_line(line)
            if line_class["type"] == "object name":
                vbo_description = VBODescription(line_class["content"])
                previous_line_type = DescriptionOfWhat.business_object
                continue
            if line_class["type"] == "mode":
                assert vbo_description, "Execution mode is not described in the correct position"
                vbo_description.mode = line_class["content"]
                continue
            if line_class["type"] == "article":
                assert vbo_description, "Not written in the correct format"
                if previous_line_type == DescriptionOfWhat.business_object:
                    vbo_description.description += line_class["content"]
                    continue
                if previous_line_type == DescriptionOfWhat.action:
                    assert latest_action, "Not written in the correct format"
                    latest_action.description += line_class["content"]
                    continue
                if previous_line_type == DescriptionOfWhat.pre_condition:
                    assert latest_action, "Not written in the correct format"
                    latest_action.pre_condition += line_class["content"]
                    continue
                if previous_line_type == DescriptionOfWhat.post_condition:
                    assert latest_action, "Not written in the correct format"
                    latest_action.post_condition += line_class["content"]
                    continue
            if line_class["type"] == "action name":
                assert vbo_description, "Not written in the correct format"
                if latest_action:
                    vbo_description = append_action_to_vbo_description(
                        latest_action, vbo_description
                    )
                latest_action = VBOActionDescription(line_class["content"])
                previous_line_type = DescriptionOfWhat.action
                continue
            if line_class["type"] == "input parameter":
                assert vbo_description and latest_action, "Not written in the correct format"
                latest_action.input_params.append(line_class["content"])
                continue
            if line_class["type"] == "output parameter":
                assert vbo_description and latest_action, "Not written in the correct format"
                latest_action.output_params.append(line_class["content"])
                continue
            if line_class["type"] == "pre condition":
                assert vbo_description and latest_action, "Not written in the correct format"
                previous_line_type = DescriptionOfWhat.pre_condition
                continue
            if line_class["type"] == "post condition":
                assert vbo_description and latest_action, "Not written in the correct format"
                previous_line_type = DescriptionOfWhat.post_condition
                continue
            # debug
            logger.debug("line: {}".format(line.strip()))
            if latest_action:
                logger.debug("latest_action: {}".format(latest_action.as_markdown()))
        else:
            #Last remaining latest_Collect action
            if latest_action:
                vbo_description = append_action_to_vbo_description(
                    latest_action, vbo_description
                )

    assert vbo_description, "Not written in the correct format"
    return vbo_description.as_markdown()


if __name__ == "__main__":
    descriptions_folder = Path("output_descriptions")
    for description_file in descriptions_folder.glob("*.txt"):
        with open(descriptions_folder / (description_file.stem + ".md"), "w") as md_f:
            md_f.write(convert_to_markdown(description_file))

Reference URL

Recommended Posts

Automatically generate Object specifications with Blue Prism
Automatically generate model relationships with Django
Beginners automatically generate documents with Pytorch's LSTM
Try to automatically generate Python documents with Sphinx
I tried to automatically generate a password with Python3
[Evangelion] Try to automatically generate Asuka-like lines with Deep Learning
Automatically generate frequency distribution table in one shot with Python
Measure and compare temperature with Raspberry Pi and automatically generate graph