Alteryx IO Discussions

Customize and extend the power of Alteryx with SDKs, APIs, custom tools, and more.

Post workflow to 2023.2 api

paul_houghton
12 - Quasar

I'm creating a Python wrapper for the Server API using HTTPX as the Python library for web queries.

When implementing the workflow post method I'm getting a 415 error indicating a media type error. If I test the same code using requests the process works and I have mapped it across to httpx correctly (I think), but I can't find why the 415 error is being produced. I think it is due to the way the file is being presented to the API but i cant identify what is incorrect. Any assistance would be appreciated

 

The code I'm using to post the workflow is: (full code on github)

 

from pathlib import Path
from typing import Any, Dict, Optional, Tuple

import httpx


def _post(
    self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs
) -> Tuple[httpx.Response, Dict[str, Any]]:
    self._update_auth_header()
    params = params or {}  # Ensure params is a dictionary
    endpoint = endpoint.lstrip("/")  # Remove leading slash if present
    response = self.http_client.post(
        f"{self.base_url}/{endpoint}", params=params, **kwargs
    )
    response.raise_for_status()
    return response, response.json()


def post_publish_workflow(
    self,
    file_path: Path,
    name: str,
    owner_id: str,
    is_public: bool = False,
    is_ready_for_migration: bool = False,
    others_may_download: bool = True,
    others_can_execute: bool = True,
    execution_mode: str = "Standard",
    workflow_credential_type: str = "Default",
    **kwargs,
) -> Tuple[httpx.Response, Dict[str, Any]]:
    file_path = Path(file_path)

    ## Removed Validation Code for brevity

    data = {
        "name": name,
        "ownerId": owner_id,
        "isPublic": is_public,
        "isReadyForMigration": is_ready_for_migration,
        "othersMayDownload": others_may_download,
        "othersCanExecute": others_can_execute,
        "executionMode": execution_mode,
        "workflowCredentialType": workflow_credential_type,
    }

    # Add keyword arguments to the data dictionary
    for key, value in kwargs.items():
        data[key] = value

    # Update the authorization header
    self._update_auth_header()
    # Make the POST request
    with open(file_path, "rb") as file:
        # content_file = file
        files = {"file": (file.name, file, "application/yxzp")}
        headers = {
            **self.http_client.headers,
            "Content-Type": "application/x-www-form-urlencoded",  # "multipart/form-data",
        }
        headers["Accept"] = "application/json"

        response = self._post(
            "v3/workflows",
            data=data,
            files=files,
            headers=headers,
        )
        return response

 

 

2 REPLIES 2
ZacharyH
Alteryx
Alteryx

Hi Paul,

 

Thanks for reaching out for assistance with the issue you are experiencing. While I do not have direct experience with the HTTPX Python library, using the code you supplied I reviewed the request using Fiddler (screenshot below) and noticed a few possible issues with the format of the request.

image.png

The first is that the Content-Type header is set to application/x-www-form-urlencoded. The Content-Type header should be multipart/form-data and it also needs to include a boundary definition. I have included an example below of what the Content-Header should look like, this is what I see in Fiddler when using the Server API tool to publish a workflow. I believe the HTTPX library will automatically set the content-type and generate the boundary if you remove "Content-Type": "application/x-www-form-urlencoded", # "multipart/form-data", from your code. 

 

Content-Type: multipart/form-data; boundary=15fb84fc67e41719d3ff120cafca795c

 

 

Another possible issue is that the filename in the form-data contains the full path to the file, rather than just the name of the file. The following is an example of what I see in Fiddler:

 

--6a4c8bcc01fd0885fa7a1b4be163ae37
Content-Disposition: form-data; name="file"; filename="C:\\temp\\Generate Rows_API Publish.yxzp"
Content-Type: application/yxzp

 

 
I would recommend changing the filename so it's just the actual file name. In the case of the example above, the filename would be "Generate Rows_API Publish". The extension is not required, but the request should still work if it is included.

paul_houghton
12 - Quasar

Hi @ZacharyH Thanks for the reply.

 

I wasn't able to find the issue in HTTPX so I tried the process in requests (postman provides an example script that works to modify) and discovered the following

 

I looked into the Content-Type and that header is not actually needed at all, removing that header clears the 415 error and is managed internally in the package.

 

The file name is my underlying problem, I was generating the file name from the open file context manager. What I didn't realise was the file.name from that property was the name is the full path description, not the Pathlib file name. changing the context manager section to:

 

with open(file_path, "rb") as file:
        # content_file = file
        files = {"file": (file_path.name, file, "application/octet-stream")}

 

Allowed me to successfully publish the workflow. now to translate the learning to using httpx.

 

Thanks