Skip to main content
Question

Issues with running python script in FME but successful in Notebooks in ArcGIS Pro

  • February 14, 2025
  • 1 reply
  • 35 views

hawaiialex
Contributor
Forum|alt.badge.img+2
import arcpy
import docx
from datetime import datetime
import os
from docx.shared import Inches

# Define the paths
inputfilename = r"Y:\GIS\FieldSummary_TEMPLATE.docx"
outputfolder = r"Y:\GIS\GeneratedDocuments"
sde = r'\\test\gis\ACCS_rw.sde'

# Set the workspace to the SDE connection
arcpy.env.workspace = sde

# Define the full feature class path
feature_class = os.path.join(sde, 'FieldWork')

# Define field mapping
field_mapping = {
    "<<PROJECT_NAME>>": "PROJECT_NAME",
    "<<FIELD_DATE>>": "FIELD_DATE", 
    "<<ARCH_CREW>>": "ARCH_CREW",
    "<<PERMIT>>": "PERMIT",
    "<<DIVISION>>": "DIVISION",
    "<<METHOD>>": "METHOD",
    "<<DIST_EXIST>>": "DIST_EXIST",
    "<<DESCRIPTION>>": "DESCRIPTION",
    "<<DIST_REQ>>": "DIST_REQ",
    "<<HISTORY>>": "HISTORY",
    "<<SUB_OB>>": "SUB_OB",
    "<<ARCH_OB>>": "ARCH_OB",
    "<<REC>>": "REC"
}

# Define the feature fields list based on field mapping plus required fields
feature_fields = list(field_mapping.values()) + ['last_edited_date', 'DEPT']

# Print debug information
print(f"Workspace: {arcpy.env.workspace}")
print(f"Feature Class: {feature_class}")
print(f"Fields to be queried: {feature_fields}")

# Rest of the code remains exactly the same as in the previous artifact, starting from here:
# Subtype to domain mapping
subtype_to_domain = {
    0: "DBLBranch",
    1: "ENGBranch",
    2: "PARKSBranch",
    3: "REFMBranch",
    4: "CMOBranch"
}

def get_domain_description(domain_name, code):
    """Get the domain description for a given code"""
    try:
        # Get the workspace from the feature class path
        workspace = os.path.dirname(arcpy.env.workspace)

        # Get the domain object
        domains = arcpy.da.ListDomains(workspace)
        target_domain = next((d for d in domains if d.name == domain_name), None)

        if target_domain and hasattr(target_domain, 'codedValues'):
            # Return the domain description if found, otherwise return the original code
            return target_domain.codedValues.get(code, str(code))
        return str(code)
    except Exception as e:
        print(f"Error getting domain description: {e}")
        return str(code)

def replace_text_in_paragraph(paragraph, old_text, new_text):
    """Replace text in a paragraph while preserving formatting"""
    if old_text in paragraph.text:
        inline = paragraph._element
        for run in paragraph.runs:
            if old_text in run.text:
                new_runs = run.text.replace(old_text, new_text)
                run.text = new_runs

def docx_find_replace_text(doc, old_text, new_text):
    """Replace text in all document parts including headers and footers"""
    # Replace in headers
    for section in doc.sections:
        for paragraph in section.header.paragraphs:
            replace_text_in_paragraph(paragraph, old_text, new_text)
        # Replace in header tables
        for table in section.header.tables:
            for row in table.rows:
                for cell in row.cells:
                    for paragraph in cell.paragraphs:
                        replace_text_in_paragraph(paragraph, old_text, new_text)

    # Replace in main document
    for paragraph in doc.paragraphs:
        replace_text_in_paragraph(paragraph, old_text, new_text)

    # Replace in tables
    for table in doc.tables:
        for row in table.rows:
            for cell in row.cells:
                for paragraph in cell.paragraphs:
                    replace_text_in_paragraph(paragraph, old_text, new_text)

    # Replace in footers
    for section in doc.sections:
        for paragraph in section.footer.paragraphs:
            replace_text_in_paragraph(paragraph, old_text, new_text)
        # Replace in footer tables
        for table in section.footer.tables:
            for row in table.rows:
                for cell in row.cells:
                    for paragraph in cell.paragraphs:
                        replace_text_in_paragraph(paragraph, old_text, new_text)

def replace_date_in_footer(doc):
    """Replace date in footer with current date"""
    current_date = datetime.now().strftime("%d %B %Y")

    for section in doc.sections:
        footer = section.footer
        # Replace in footer paragraphs
        for paragraph in footer.paragraphs:
            if "<<DATE2>>" in paragraph.text:
                # If the paragraph contains only the date placeholder
                if paragraph.text.strip() == "<<DATE2>>":
                    paragraph.text = current_date
                else:
                    # Handle cases where there might be other text in the paragraph
                    for run in paragraph.runs:
                        if "<<DATE2>>" in run.text:
                            run.text = run.text.replace("<<DATE2>>", current_date)

        # Replace in footer tables
        for table in footer.tables:
            for row in table.rows:
                for cell in row.cells:
                    for paragraph in cell.paragraphs:
                        if "<<DATE2>>" in paragraph.text:
                            # If the paragraph contains only the date placeholder
                            if paragraph.text.strip() == "<<DATE2>>":
                                paragraph.text = current_date
                            else:
                                # Handle cases where there might be other text in the paragraph
                                for run in paragraph.runs:
                                    if "<<DATE2>>" in run.text:
                                        run.text = run.text.replace("<<DATE2>>", current_date)

def add_header_footer_if_missing(doc):
    """Add header and footer sections if they don't exist"""
    for section in doc.sections:
        # Add header if it doesn't exist
        header = section.header
        if not header.paragraphs:
            header.add_paragraph()

        # Add footer if it doesn't exist
        footer = section.footer
        if not footer.paragraphs:
            footer.add_paragraph()
            # Add the date placeholder to the first paragraph
            footer.paragraphs[0].text = "<<DATE2>>"

# Main execution
try:
    # Create output folder if it doesn't exist
    if not os.path.exists(outputfolder):
        os.makedirs(outputfolder)

    with arcpy.da.SearchCursor(feature_class, feature_fields) as cursor:
        for feature in cursor:
            last_edited_date = feature[-2]
            subtype_code = feature[-1]

            if last_edited_date is None:
                print("Skipping record with no last_edited_date.")
                continue

            project_name_index = feature_fields.index("PROJECT_NAME")
            project_name = feature[project_name_index]

            if not project_name:
                print("Skipping record with no project name.")
                continue

            output_filename = os.path.join(outputfolder, f"{project_name}_FieldSummary.docx")

            if os.path.exists(output_filename):
                doc_mod_time = datetime.fromtimestamp(os.path.getmtime(output_filename))
                if doc_mod_time >= last_edited_date:
                    print(f"Skipping {output_filename}, already up-to-date.")
                    continue

            doc = docx.Document(inputfilename)

            # Ensure header and footer exist
            add_header_footer_if_missing(doc)

            for placeholder, field in field_mapping.items():
                field_index = feature_fields.index(field)
                value = feature[field_index]

                # Handle DIVISION with subtype-specific domains
                if field == "DIVISION":
                    domain_name = subtype_to_domain.get(subtype_code)
                    if domain_name:
                        value = get_domain_description(domain_name, value)

                # Convert None to empty string
                if value is None:
                    value = ""

                # Replace placeholders in the template
                docx_find_replace_text(doc, placeholder, str(value))

            # Replace date in the footer
            replace_date_in_footer(doc)

            doc.save(output_filename)
            print(f"Created or updated: {output_filename}")

    print("Process completed successfully.")

except Exception as e:
    print(f"An error occurred: {str(e)}")
    raise

Hi all,

I’ve been dealing with a confusing predicament. I have the above python script running successfully in Notebooks in ArcGIS Pro.

This script creates a new docx or updates an exisitng docx replacing placeholders within the template docx with the appropriate fields from the feature class in an enterprise geodatabase. This includes relevant info in the header and footer of the template docx. 

It runs without a problem and successfully accomplishes the workflow. However, when I plug it in the Start Up Script Portion of the Creator or PythonCreator in FME, I am getting an error stating:

An error occurred: 'Section' object has no attribute 'header' Python Exception <AttributeError>: 'Section' object has no attribute 'header'

I have tried many different iterations of the script but am still running into that error when running it. I have already accounted for experimenting with the python compatibility portion and ensuring it is running ArcGIS Python 3.6+ which is the appropriate version. 

Has anybody else come across this issue whereby a script works well in Notebooks/ArcGIS Pro and not when plugged into FME? 

1 reply

bwn
Evangelist
Forum|alt.badge.img+26
  • Evangelist
  • February 16, 2025

Any script in PythonCaller and PythonCreator must have a Class with a name equal to the “Class to Process Features” Parameter.  Otherwise there is no “Object” for FME to instantiate.

It is generally best to follow the template script and put your code into the blocks into the handler functions such as __init__ , input, or close that are called by FME on particular events such as a Feature entering the transformer etc.
 

 


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings