0

I am trying to use PySimpleGUI and docxtpl to create a GUI which will render user defined inputs into a Word Document.

I would like to use TabGroup to organize the GUI, however it seems this is causing an error on doc.render(values)

Traceback (most recent call last): File "C:\Users\SESA484891\Desktop\doc render practice\DP-docrenderpractice.py", line 27, in doc.render(values) TypeError: keywords must be strings

Does each tab have its own dictionary? How do I access it?

I have tried simplifying my code to remove TabGroup and was able to successfully render values from GUI to a Word document.

from pathlib import Path
import PySimpleGUI as sg
from docxtpl import DocxTemplate

document_path = Path(__file__).parent / "DP-docrenderpractice.docx"
doc = DocxTemplate(document_path)

# TAB
tab1 = [
    [sg.Text("Proposal Name:"),sg.Input(key="PROPOSAL_NAME",size=(30,0))]
]
# LAYOUT 
layout = [[sg.TabGroup([
    [sg.Tab('Tab', tab1)]])],
    [sg.Button("Create Template"), sg.Exit()],
]
##########################

window = sg.Window("Doc Render Practice", layout)

while True:
    event, values = window.read()
    if event == sg.WIN_CLOSED or event == "Exit":
        break
    if event == "Create Template":
        # Render the template, save new word document & inform user
        doc.render(values)
        output_path = Path(__file__).parent / f"DP-{values['PROPOSAL_NAME']}.docx"
        doc.save(output_path)
        sg.popup("File saved", f"File has been saved here: {output_path}")

window.close()

GUI

Full Traceback

=============================================================================RESTART: C:\Users\SESA484891\Desktop\doc render practice\DP-docrenderpractice.py==================================================
Traceback (most recent call last):
  File "C:\Users\ SESA484891\Desktop\doc render practice\DP-docrenderpractice.py", line 27, in <module> 
    doc.render (values)
  File "C:\Users\ SESA484891\AppData\Local\Packages\Python Software Foundation. Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\docxtpl\template.py", line 368, in render 
    xml src= self.build_xml (context, jinja_env)
  File "C:\Users\ SESA484891\AppData\Local\Packages\Python Software Foundation. Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\docxtpl\template.py", line 315, in build_xml 
    xml = self.render_xml_part (xml, self.docx._part, context, jinja_env)
  File "C:\Users\ SESA484891\AppData\Local\Packages\Python Software Foundation. Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\docxtpl\template.py", line 239, in render_xml_part 
    dst_xml = template.render (context) 
  File "C:\Users\ SESA484891\AppData\Local\Packages\PythonSoftware Foundation. Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\jinja2\environment.py", line 1296, in render 
    ctx = self.new_context (dict (*args, **kwargs))
  File "C:\Users\ SESA484891\AppData\Local\Packages\PythonSoftware Foundation. Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\jinja2\environment.py", line 1388, in new_context 
    return new context 
  File "C:\Users\SESA484891\AppData\Local\Packages\PythonSoftware Foundation. Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\jinja2\runtime.py", line 106, in new_context 
     parent = dict (globals or (), **vars)
TypeError: keywords must be strings

EDIT:Solved!!!

As others have suggested, I printed out the values dictionary after window.read() to get a better understanding.

PymSimpleGUI adds a key “0” as the index of the currently selected Tab under TabGroup. This conflicts with docxtpl as render requires all keys to be str. The simple fix is to dict.popitem() after the window.read() to delete the last element I.e. the Tab key.

Evan Lee
  • 1
  • 2
  • 1
    Maybe you need to specify the key of TabGroup element to a string, or it will be an integer or 0 which assigned by PySimpleGUI. – Jason Yang Aug 28 '23 at 03:32
  • I suggest adding a `print(event, values)` immediately following your call to `window.read()`. This will show you the event and the values dictionary so that you know how to format the key to look up values, etc. The values dictionary returned from `window.read()` is flat. Even if your layout is nested and uses container elements, the values dictionary will return the keys defined in the element. It's not organized by tab or any other hierarchy. – Mike from PSG Aug 28 '23 at 10:47
  • Another "fix" that doesn't involve deleting keys, is to add a key to your TabGroup so that it's not `0`. Add one that's text if that's what you need. `sg.TabGroup([[sg.Tab('Tab', tab1)]], key='-TAB GROUP-')` – Mike from PSG Sep 01 '23 at 14:00

2 Answers2

0

Adding a print(event, values) immediately following your call to window.read() will tell you a lot about the values input and the format of the values dictionary.

I've commented out the docxtpl portion of your code and added a print.

from pathlib import Path
import PySimpleGUI as sg
# from docxtpl import DocxTemplate

document_path = Path(__file__).parent / "DP-docrenderpractice.docx"
# doc = DocxTemplate(document_path)

# TAB
tab1 = [
    [sg.Text("Proposal Name:"),sg.Input(key="PROPOSAL_NAME",size=(30,0))]
]
# LAYOUT
layout = [[sg.TabGroup([
    [sg.Tab('Tab', tab1)]])],
    [sg.Button("Create Template"), sg.Exit()],
]
##########################

window = sg.Window("Doc Render Practice", layout)

while True:
    event, values = window.read()
    print(event, values)
    if event == sg.WIN_CLOSED or event == "Exit":
        break
    if event == "Create Template":
        # Render the template, save new word document & inform user
        # doc.render(values)
        output_path = Path(__file__).parent / f"DP-{values['PROPOSAL_NAME']}.docx"
        # doc.save(output_path)
        sg.popup("File saved", f"File has been saved here: {output_path}")

window.close()

enter image description here

Entering test into the Input element and clicking the Create Template button, I see this printed:

Create Template {'PROPOSAL_NAME': 'test', 0: 'Tab'}

The TabGroup element has a key that has defaulted to 0 because one wasn't in the layout.

I get this popup as well:

enter image description here

Mike from PSG
  • 5,312
  • 21
  • 39
-3

The error you're encountering, TypeError: keywords must be strings, occurs because the values dictionary that you're passing to doc.render(values) should only contain string keys. The issue might be related to the fact that PySimpleGUI's values dictionary can contain mixed key types when using a TabGroup, as each tab might have its own dictionary.

To access the values from the tabs, you should use the dictionaries associated with each tab's content. Here's how you can modify your code to work with a TabGroup and access the values correctly:

from pathlib import Path
import PySimpleGUI as sg
from docxtpl import DocxTemplate

document_path = Path(__file__).parent / "DP-docrenderpractice.docx"
doc = DocxTemplate(document_path)

# TAB
tab1 = [
    [sg.Text("Proposal Name:"), sg.Input(key="PROPOSAL_NAME", size=(30, 0))]
]

# LAYOUT
layout = [
    [sg.TabGroup([
        [sg.Tab('Tab', tab1)]
    ])],
    [sg.Button("Create Template"), sg.Exit()],
]

window = sg.Window("Doc Render Practice", layout)

while True:
    event, values = window.read()
    if event == sg.WIN_CLOSED or event == "Exit":
        break
    if event == "Create Template":
        # Access values from the tab's dictionary
        tab1_values = values['Tab']
        
        # Render the template, save new word document & inform user
        doc.render(tab1_values)
        output_path = Path(__file__).parent / f"DP-{tab1_values['PROPOSAL_NAME']}.docx"
        doc.save(output_path)
        sg.popup("File saved", f"File has been saved here: {output_path}")

window.close()

In this modified code, I've accessed the values dictionary associated with the 'Tab' tab using values['Tab']. This will allow you to pass the correct dictionary containing only string keys to the doc.render() function.

Danyal Imran
  • 2,515
  • 13
  • 21