Playing Nice with Power Automate


By Joseph Carboni

I’m generally a fan of roll-your-own-everything when trying to learn, but sometimes I just want things to work. Like, right now. The drawback to using out-of-the-box products is that the ideal way to use them isn’t obvious, at least in my experience. Before long, I find myself scrolling through forums, pleading with an LLM for help, and just not having a good time in general. (Wait, wasn’t I trying to take the “easy” route?)

In my case, I had a PDF document parsing strategy set up behind an API endpoint. I wanted to monitor my email stream for emails that met a certain criteria, and if those criterial were met, yank the PDF attachment, post it to the API endpoint, and finally send out an email with the endpoint’s response, an HTML document.

Why Power Automate?

Sure, I have gone down this particular roll-your-own path before: wrestling with Microsoft Graph, registering my application in Azure for credentials, and polling my email inbox. Did I want to do all of that again? No, not really.

So, I went over to Microsoft Power Automate, because my email is through Microsoft and this program abstracts away the details of getting access to my email stream. Actually, (Linux Stans, cover your ears,) the flow builder is pretty nice.

I made this flow.

The real wildcard here will be the API request right in the middle (“SCA API Request”). Power Automate has a built-in HTTP action (which does require a Pro License.) In building any action, you can take data from any of your previous actions and include them in your current action. This request is the version that I ended up using, taking Content Bytes and Content Type from the previous Get Attachment step.

Hang-ups

Form Data

The SCA API is built with FastAPI. If you’re familiar with FastAPI, you know it comes with File and UploadFile. Both can be put into the function signature with the expectation that the file will come as form data. So, why did I put the file attachment in the body as JSON?

I made several attempts to use UploadFile and hand-roll the multipart/form-data in the Body field. I even got LLMs to help me double-check that it was correct, boundaries properly defined and all. But, something terrible was happening when the request sent: FastAPI complained that the form data didn’t have a “CR” (Carriage Return) after the boundary.

I tried various ways of manually adding it in, attempting to avoid having it escaped out. No luck. I couldn’t tell if the CR was even the real issue or just the result of a different issue. So, I fell back to JSON.

Content “bytes”

The second hang-up I ran into was with the format of Content Bytes. You may assume, as I did, that this will be the raw bytes. Well, you’ve been fooled. The content isn’t really bytes, it is a base64-encoded string! (In retrospect, this actually makes sense.)

So, when you bring in the file data from the JSON body, you’ll need to hit it with b64decode.

from io import BytesIO
from base64 import b64decode
from pydantic import BaseModel
from fastapi import HTTPException, status, APIRouter

router = APIRouter(prefix='/some-prefix')

class NewFile(BaseModel):
    file: str
    content_type: str

@router.post("/confirmations")
async def new_confirmation(file: NewFile) -> _TemplateResponse:
    if file.content_type != "application/pdf":
        raise HTTPException(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)

    data = b64decode(file.file)
    new_conf = BytesIO(data)

    # .. rest of code ... generating html template values ...
    return templates.TemplateResponse("template.html", variable_values)
Custom headers

The third hang-up was with headers, particularly when I tried to add one in the Headers field of the request builder. I attempted to add the Content -Type there instead of the JSON body. When I did so, however, FastAPI was returning 422 – Unprocessable Entity. In the response content, Pydantic complained that the request body was not a valid dictionary.

Why? Honestly, I can’t say. Rather than banging my head against the wall, I abandoned this approach and just kept the file’s content type in the JSON body, which seemed to work fine.

Conclusion

The behavior you get is opaque when you’re going a little outside of the box. I admit, I could have gotten more information about what was going on by re-deploying the API with some print-statements here and there, maybe even by pointing the request somewhere else for debugging purposes, but that sounded like a lot of extra work for little reward. If any of you know more about this, I’d be happy to hear about your experiences.

It’s safe to say that keeping it simple is ideal. Stick with putting your data in the JSON body and don’t try to get too clever.

ABOUT THE AUTHOR

Joseph Carboni is a multifaceted programmer with a background in bioinformatics, neuroscience, and sales, now focusing on Python development. He developed a ribosomal loading model and contributed to a neuroscience paper before transitioning to a six-year sales career, enhancing his understanding of business and client relations. Currently, he’s a Python Developer at Shupe, Carboni & Associates, improving business processes, and runs Carboni Technology for independent tech projects. Joseph welcomes collaborations and discussions via LinkedIn (Joseph Carboni), X (@JoeCarboni1), or email (joe@carbonitech.com).


PUBLISH YOUR WRITINGS HERE!

We are always looking to publish your writings on the pyATL website. All content must be related to Python, non-commercial (pitches), and comply with out code of conduct.
If you’re interested, reach out to the editors at hello@pyatl.dev