How to Programmatically Access Methods and Types with SUDS: Inspect SOAP Service Methods and Determine Factory Object Types for Dynamic UI Form Generation in Python

SOAP (Simple Object Access Protocol) remains a prevalent protocol for web services, especially in enterprise environments where strict standards and complex data structures are required. Interacting with SOAP services programmatically often involves understanding their methods, parameters, and data types—tasks that can be tedious if done manually. This is where SUDS, a lightweight SOAP client library for Python, shines.

In this blog, we’ll explore how to use SUDS to:

  • Inspect a SOAP service’s WSDL (Web Services Description Language) to discover available methods and their parameters.
  • Determine the structure of complex data types (defined in the WSDL) using SUDS’s factory objects.
  • Leverage this information to dynamically generate user interface (UI) forms, enabling users to input data and interact with SOAP services without manual coding.

Whether you’re building a tool to simplify SOAP service testing or integrating a SOAP service into a dynamic application, this guide will equip you with the skills to automate method inspection and form generation.

Table of Contents#

  1. Prerequisites
  2. Installing SUDS
  3. Understanding WSDL and SOAP Services
  4. Loading a SOAP Service with SUDS
  5. Inspecting SOAP Service Methods Programmatically
  6. Factory Objects in SUDS: Creating Complex Types
  7. Determining Factory Object Types and Structures
  8. Dynamic UI Form Generation: From Method Metadata to User Inputs
  9. Handling Edge Cases: Optional Fields, Arrays, and Enums
  10. Example Workflow: End-to-End Dynamic Form Generation
  11. Conclusion
  12. References

Prerequisites#

Before diving in, ensure you have:

  • Python 3.6+: SUDS works with Python 3 (we’ll use suds-jurko, a maintained fork of the original SUDS library).
  • Basic SOAP/WSDL Knowledge: Familiarity with terms like WSDL, SOAP envelope, complex type, and service port will help.
  • A SOAP Service WSDL: For testing, use a public WSDL (e.g., Calculator Service or Weather Service).

Installing SUDS#

The original SUDS library is outdated, so we’ll use suds-jurko, a community-maintained fork with Python 3 support. Install it via pip:

pip install suds-jurko  

Understanding WSDL and SOAP Services#

A WSDL (Web Services Description Language) is an XML document that defines a SOAP service’s:

  • Services: Collections of related ports (endpoints).
  • Ports: Endpoints where the service is accessible (e.g., http://example.com/soap/service).
  • Methods (Operations): Actions the service exposes (e.g., Add, GetWeather).
  • Data Types: Simple types (string, int) and complex types (custom objects like Person or Address).

SUDS parses the WSDL to generate a client, allowing you to interact with the service and inspect its structure programmatically.

Loading a SOAP Service with SUDS#

To start, create a SUDS client by pointing it to the WSDL URL. This client will parse the WSDL and expose methods to inspect the service.

Example: Load a WSDL#

from suds.client import Client  
 
# Public calculator service WSDL (adds/subtracts numbers)  
wsdl_url = "http://www.dneonline.com/calculator.asmx?WSDL"  
 
# Create a SUDS client  
client = Client(wsdl_url)  
 
# Print basic service info (optional but helpful for debugging)  
print("Service Name:", client.wsdl.services[0].name)  
print("Port Name:", client.wsdl.services[0].ports[0].name)  
print("Endpoint URL:", client.wsdl.services[0].ports[0].location)  

Output:

Service Name: Calculator  
Port Name: CalculatorSoap  
Endpoint URL: http://www.dneonline.com/calculator.asmx  

Inspecting SOAP Service Methods Programmatically#

Once the client is loaded, you can list all methods exposed by the service and inspect their parameters, return types, and constraints.

Step 1: List All Methods#

Services expose methods via their ports. Use client.wsdl.services[0].ports[0].methods to get a dictionary of methods (keys are method names, values are method metadata).

# Get all methods for the first port of the first service  
methods = client.wsdl.services[0].ports[0].methods  
print("Available Methods:", list(methods.keys()))  

Output (for the calculator service):

Available Methods: ['Add', 'Subtract', 'Multiply', 'Divide']  

Step 2: Inspect a Specific Method’s Parameters#

To generate a UI form, you need to know:

  • The method’s input parameters (names and types).
  • Whether parameters are required or optional.

Use method.soap.input.body.parts to get parameter details for a method:

# Inspect the "Add" method  
method_name = "Add"  
method = methods[method_name]  
 
# Get input parameters (SOAP body parts)  
input_parts = method.soap.input.body.parts  
 
# Extract parameter names and types  
params = []  
for part in input_parts:  
    # part.element is the XSD element defining the parameter  
    param_name = part.element.name  
    param_type = part.element.type.name  # e.g., "int"  
    params.append({"name": param_name, "type": param_type})  
 
print(f"Parameters for {method_name}: {params}")  

Output:

Parameters for Add: [{'name': 'intA', 'type': 'int'}, {'name': 'intB', 'type': 'int'}]  

Factory Objects in SUDS: Creating Complex Types#

SOAP services often use complex types (custom objects like User or Order). Unlike simple types (int, string), complex types must be instantiated using SUDS’s factory to ensure compatibility with the service.

What is client.factory?#

The client.factory object lets you create instances of complex types defined in the WSDL. For example, if the WSDL defines a Person type with name (string) and age (int), use client.factory.create("Person") to instantiate it.

Example: Creating a Complex Type#

Suppose a service has a Person type:

<!-- Excerpt from WSDL -->  
<xsd:complexType name="Person">  
  <xsd:sequence>  
    <xsd:element name="Name" type="xsd:string"/>  
    <xsd:element name="Age" type="xsd:int"/>  
  </xsd:sequence>  
</xsd:complexType>  

Use the factory to create a Person object:

# Create a Person object via the factory  
person = client.factory.create("Person")  
person.Name = "Alice"  
person.Age = 30  
 
print(person)  # Output: Person(Name='Alice', Age=30)  

Determining Factory Object Types and Structures#

To generate forms for complex types, you need their structure: field names, data types, and constraints (e.g., min/max length, required fields).

SUDS stores type definitions in client.wsdl.types, a list of suds.xsd.sxbasic.Schema objects. Use this to extract details for any type.

Step 1: Get Type Definitions#

Use client.wsdl.types to access all XSD types. To find a specific type (e.g., Person), filter by name:

def get_type_definition(type_name):  
    """Retrieve the XSD type definition for a given type name."""  
    for schema in client.wsdl.types:  
        type_def = schema.get_type(type_name)  
        if type_def:  
            return type_def  
    return None  
 
# Get definition for "Person"  
person_type = get_type_definition("Person")  
print("Person Type Definition:", person_type)  

Step 2: Extract Fields and Their Types#

Complex types have children() that define their fields. For each child, extract:

  • name: Field name (e.g., "Name").
  • type: Field data type (e.g., "string").
  • min_occurs/max_occurs: Whether the field is required (min_occurs=1) or optional (min_occurs=0).
def get_type_fields(type_def):  
    """Extract fields (name, type, required) from a complex type definition."""  
    fields = []  
    for child in type_def.children():  
        # Child is an XSD element (e.g., <xsd:element name="Name" type="xsd:string"/>)  
        field_name = child.name  
        field_type = child.type.name  
        is_required = child.min_occurs > 0  # min_occurs=1 means required  
        fields.append({  
            "name": field_name,  
            "type": field_type,  
            "required": is_required  
        })  
    return fields  
 
# Get fields for "Person"  
person_fields = get_type_fields(person_type)  
print("Person Fields:", person_fields)  

Output:

Person Fields: [  
  {'name': 'Name', 'type': 'string', 'required': True},  
  {'name': 'Age', 'type': 'int', 'required': True}  
]  

Dynamic UI Form Generation: From Method Metadata to User Inputs#

With method parameters and type structures in hand, we can generate a UI form dynamically. We’ll use Tkinter (Python’s built-in GUI library) for simplicity, but you could adapt this to web frameworks like Flask/Django or desktop tools like PyQt.

Step 1: Map Data Types to UI Widgets#

Different data types require different input widgets:

SOAP TypeTkinter WidgetExample
stringEntry (text input)Single-line text box
int/floatEntry with validationNumeric input only
dateEntry (or DateEntry)YYYY-MM-DD formatted input
booleanCheckbuttonCheckbox for True/False
Complex TypeNested FrameGroup of widgets for sub-fields

Step 2: Recursive Form Generation#

For nested complex types (e.g., Address inside Person), recursively generate fields. Here’s a Tkinter example:

import tkinter as tk  
from tkinter import ttk  
 
def create_input_widget(parent, field):  
    """Create a Tkinter widget for a field based on its type."""  
    frame = ttk.Frame(parent)  
    frame.pack(fill="x", padx=5, pady=2)  
 
    # Label (show field name and required status)  
    label_text = f"{field['name']} {'*' if field['required'] else ''}"  
    ttk.Label(frame, text=label_text).pack(side="left", padx=5)  
 
    # Widget based on type  
    if field["type"] in ["string", "int", "float", "date"]:  
        widget = ttk.Entry(frame)  
        widget.pack(side="left", fill="x", expand=True, padx=5)  
    elif field["type"] == "boolean":  
        widget = ttk.Checkbutton(frame)  
        widget.pack(side="left", padx=5)  
    else:  
        # Assume complex type: recursively generate fields  
        widget = ttk.LabelFrame(frame, text=field["type"])  
        widget.pack(side="left", fill="x", expand=True, padx=5)  
        complex_type = get_type_definition(field["type"])  
        if complex_type:  
            complex_fields = get_type_fields(complex_type)  
            for sub_field in complex_fields:  
                create_input_widget(widget, sub_field)  
    return widget  
 
def generate_form(method_name):  
    """Generate a Tkinter form for a SOAP method."""  
    root = tk.Tk()  
    root.title(f"SOAP Method: {method_name}")  
 
    # Get method parameters  
    method = client.wsdl.services[0].ports[0].methods[method_name]  
    input_parts = method.soap.input.body.parts  
 
    # Generate widgets for each parameter  
    widgets = {}  
    for part in input_parts:  
        param_name = part.element.name  
        param_type = part.element.type.name  
 
        # Check if parameter is a complex type  
        type_def = get_type_definition(param_type)  
        if type_def and type_def.complex:  
            # Complex type: generate nested fields  
            fields = get_type_fields(type_def)  
            for field in fields:  
                widgets[field["name"]] = create_input_widget(root, field)  
        else:  
            # Simple type: generate basic widget  
            field = {  
                "name": param_name,  
                "type": param_type,  
                "required": part.element.min_occurs > 0  
            }  
            widgets[param_name] = create_input_widget(root, field)  
 
    # Add a "Submit" button (to call the SOAP method)  
    ttk.Button(root, text="Submit", command=lambda: submit_form(method_name, widgets)).pack(pady=10)  
 
    root.mainloop()  
 
def submit_form(method_name, widgets):  
    """Extract values from widgets and call the SOAP method."""  
    # Extract input values (simplified: assumes widgets are Entry/Checkbutton)  
    params = {}  
    for name, widget in widgets.items():  
        if isinstance(widget, ttk.Entry):  
            params[name] = widget.get()  
        elif isinstance(widget, ttk.Checkbutton):  
            params[name] = widget.get()  
 
    # Call the SOAP method (e.g., client.service.Add(intA=1, intB=2))  
    result = getattr(client.service, method_name)(**params)  
    print(f"Result: {result}")  

Handling Edge Cases: Optional Fields, Arrays, and Enums#

Real-world SOAP services often include:

  • Optional Fields: Use min_occurs=0 to mark fields as optional (grey out or hide in UI).
  • Arrays: Types with max_occurs="unbounded" (e.g., string[]). Add "Add Item" buttons to let users input multiple values.
  • Enums: Types with fixed values (e.g., StatusEnum: [Active, Inactive]). Use dropdowns (ttk.Combobox) with allowed values.

Example: Handling Enums#

To detect enums, check if a type has restriction.base and restriction.enumeration:

def is_enum(type_def):  
    """Check if a type is an enumeration."""  
    return hasattr(type_def, "restriction") and hasattr(type_def.restriction, "enumeration")  
 
def get_enum_values(type_def):  
    """Get allowed values for an enum type."""  
    return [enum.value for enum in type_def.restriction.enumeration]  

Example Workflow: End-to-End Dynamic Form Generation#

Let’s tie it all together with the calculator service’s Add method:

1.** Load the WSDL : client = Client("http://www.dneonline.com/calculator.asmx?WSDL").
2.
Inspect Add method : Parameters are intA (int) and intB (int), both required.
3.
Generate Form : generate_form("Add") creates a window with two numeric inputs and a "Submit" button.
4.
Submit**: Enter intA=5 and intB=3, click "Submit"—the result 8 prints to the console.

Conclusion#

SUDS simplifies programmatically inspecting SOAP services and generating dynamic UIs. By parsing WSDL metadata, extracting method parameters, and mapping complex types to UI widgets, you can build tools that adapt to any SOAP service’s structure.

Key takeaways:

  • Use client.wsdl to inspect services, methods, and types.
  • client.factory creates complex type instances for service calls.
  • Recursively parse type definitions to handle nested objects.
  • Map SOAP types to UI widgets (Entry, Checkbutton, Combobox) for dynamic forms.

References#