<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Cloudmersive on Medium]]></title>
        <description><![CDATA[Stories by Cloudmersive on Medium]]></description>
        <link>https://medium.com/@cloudmersive?source=rss-2f1582e63183------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*3v0ktJZ-a-uV7KOqs9vF6Q.png</url>
            <title>Stories by Cloudmersive on Medium</title>
            <link>https://medium.com/@cloudmersive?source=rss-2f1582e63183------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 21:30:43 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@cloudmersive/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How to Screen Uploaded Images for Explicit Content in Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-screen-uploaded-images-for-explicit-content-in-python-8dedabb45730?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/8dedabb45730</guid>
            <category><![CDATA[application-security]]></category>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[content-moderation]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Thu, 12 Nov 2026 19:26:01 GMT</pubDate>
            <atom:updated>2026-11-12T19:26:01.501Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xkn2AyoM0H4DQcuzeCudwQ.jpeg" /></figure><p>Any field that accepts image uploads is an open invitation for explicit content. Of course, not <em>everyone </em>will interpret your upload fields as such — but unfortunately, as ever, we must to march to the beat of the most erratic and unpredictable user.</p><p>It’s a well-established fact that eyeballing every upload doesn’t scale, so we’ll of course need an automatic score to gate uploads on. Thankfully, in the bustling age of AI, there’s no shortage of powerful solutions we can count on to provide this exact service.</p><p>Here, we’ll handle explicit content detection via the Cloudmersive Image Recognition API’s Python client.</p><h4>Installing and configuring the client</h4><p>Let’s begin by installing the latest version of the Image API client:</p><pre>pip install cloudmersive-image-api-client</pre><p>And let’s now import resources &amp; configure the client using our Cloudmersive API key:</p><pre>import os<br>import cloudmersive_image_api_client<br><br>config = cloudmersive_image_api_client.Configuration()<br>config.api_key[&quot;Apikey&quot;] = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>nsfw_api = cloudmersive_image_api_client.NsfwApi(<br>    cloudmersive_image_api_client.ApiClient(config)<br>)</pre><p>Missing an API key? You can get one for free by creating a free account on the Cloudmersive website (this allows up to 800 API calls per month with zero commitments). It must be said, however, that explicit content detection is a resource-intensive operation, so you might consider small business or enterprise-tier keys if you’re dealing with a high-volume workflow.</p><h4>Scoring an image (defining its explicitness)</h4><p>Through this API, we’re working with a classier that returns a score from 0.0 (clearly safe) to 1.0 (clearly explicit), with the racy middle ground living inbetween:</p><pre>def nsfw_score(path):<br>    result = nsfw_api.nsfw_classify(path)<br>    return result.score</pre><h4>Deciding what to allow based on the score</h4><p>To use this API effectively, we need to pick a threshold that matches our own specific tolerance. As a rough guide, we can consider anything under 0.2 as safe, around 0.7 as racy, and above 0.8 as explicit; so 0.5 is a reasonably strict cutoff to stay on the safe side.</p><pre>def is_allowed(path, threshold=0.5):<br>    return nsfw_score(path) &lt; threshold<br><br>if not is_allowed(&quot;uploads/avatar.jpg&quot;):<br>    reject(&quot;This image was flagged as inappropriate.&quot;)</pre><h4>Conclusion</h4><p>Yep, we’re already done — that’s all there is to it. Just plug that into your workflow and let automated decisions filter out the bad stuff. If you find that too much borderline content is being blocked, all you need to do is adjust your score threshold in the minimal code we’ve written, and you’re all set.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8dedabb45730" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Generate Preview Thumbnails for Uploaded Documents in Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-generate-preview-thumbnails-for-uploaded-documents-in-python-90c725a2128e?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/90c725a2128e</guid>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[image-processing]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Thu, 12 Nov 2026 16:10:11 GMT</pubDate>
            <atom:updated>2026-11-12T16:10:11.648Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BthtjrcEjaA2lhhEjiJ-kQ.jpeg" /></figure><p>You’ve got a list of uploaded documents, and you want a small visual preview beside each one (the way a file browser shows them). Good choice.</p><p>The only trouble is that rendering a Word doc or PDF to an image yourself means wrangling a headless renderer for every format your platform accepts. That’s not worth the trouble.</p><p>Thankfully, there’s an easier way: we’ll generate PNG thumbnails with a few lines of code using the Cloudmersive Convert API.</p><h4>Installing &amp; configuring the client</h4><p>First, let’s install the client with the below pip command:</p><pre>pip install cloudmersive-convert-api-client</pre><p>Next, let’s import the resources we need &amp; configure our API key:</p><pre>import os<br>import cloudmersive_convert_api_client<br><br>config = cloudmersive_convert_api_client.Configuration()<br>config.api_key[&quot;Apikey&quot;] = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>convert_api = cloudmersive_convert_api_client.ConvertDocumentApi(<br>    cloudmersive_convert_api_client.ApiClient(config)<br>)</pre><p>We can get free API key by signing up for a free account on the Cloudmersive website; that’ll give us a limit of 800 API calls/month with no commitments.</p><h4>Creating the thumbnail</h4><p>The “autodetect thumbnail” endpoint we’ll be using figures out the document’s format on its own and returns a PNG as bytes. You just have to set the maximum dimensions; it scales to fit automatically:</p><pre>def make_thumbnail(path, width=128, height=128):<br>    return convert_api.convert_document_autodetect_to_thumbnail(<br>        path, max_width=width, max_height=height<br>    )</pre><h4>Saving the thumbnail</h4><p>This part’s just as easy. We’ll use a quick helper to handle saving:</p><pre>def save_thumbnail(path, out_path, width=128, height=128):<br>    png = make_thumbnail(path, width, height)<br>    with open(out_path, &quot;wb&quot;) as f:<br>        f.write(png)<br><br>save_thumbnail(&quot;uploads/report.docx&quot;, &quot;thumbs/report.png&quot;)</pre><p>A Word doc, a PDF, a spreadsheet, etc. — each comes back as a ready-to-display PNG at the size you specified.</p><h4>Conclusion</h4><p>One call turns any uploaded document into a preview image with no per-format rendering on your side. Just point this at whever users send through and get back a thumbnail you can drop straight into your UI.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=90c725a2128e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Turn Uploaded Excel Files into Clean JSON in Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-turn-uploaded-excel-files-into-clean-json-in-python-22626342d0db?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/22626342d0db</guid>
            <category><![CDATA[api]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[json]]></category>
            <category><![CDATA[excel]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 19:26:01 GMT</pubDate>
            <atom:updated>2026-11-10T19:26:01.521Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2HPgR_rrk9uGwkjMHhMipQ.jpeg" /></figure><p>People like spreadsheets, but your app likes structured data. The trouble is that parsing XLSX for your app with custom code is no fun; it’s fiddly, and it can break the moment an Excel sheet defies our expectations.</p><p>It’s ultimately far less work to convert spreadsheets to JSON in one quick API call, and then tidy the result.</p><p>In our unbiased opinion, it’s best to handle this in Python using the Cloudmersive Convert API client. Let’s walk through that now.</p><h4>Installing and configuring the client</h4><p>To begin, let’s run the following pip command to install the client:</p><pre>pip install cloudmersive-convert-api-client</pre><p>After that, let’s configure the client with our API key:</p><pre>import os<br>import cloudmersive_convert_api_client<br><br>config = cloudmersive_convert_api_client.Configuration()<br>config.api_key[&quot;Apikey&quot;] = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>data_api = cloudmersive_convert_api_client.ConvertDataApi(<br>    cloudmersive_convert_api_client.ApiClient(config)<br>)</pre><p>We can get a free API key by creating a free account on the Cloudmersive website (this allows 800 API calls/month with no commitments).</p><h4>Converting the spreadsheet to JSON</h4><p>The XLSX-to-JSON endpoint we’ll be using reads the sheet and hands back a row of objects, each keyed by the column headers. Ther’es no cell math required.</p><pre>def xlsx_to_rows(path):<br>    return data_api.convert_data_xlsx_to_json(path)</pre><h4>Cleaning the output</h4><p>We might run into an issue of spreadsheets carrying blank rows and stray whitespace — so let’s include a pass that trims strings and drops rows that are entirely empty:</p><pre>def clean(rows):<br>    cleaned = []<br>    for row in rows:<br>        row = {k: (v.strip() if isinstance(v, str) else v) for k, v in row.items()}<br>        if any(v not in (None, &quot;&quot;) for v in row.values()):<br>            cleaned.append(row)<br>    return cleaned</pre><h4>Putting it together</h4><pre>def load_spreadsheet(path):<br>    return clean(xlsx_to_rows(path))<br><br>for record in load_spreadsheet(&quot;uploads/orders.xlsx&quot;):<br>    print(record)</pre><p>Now, an uploaded workbook comes out as a clean list of dictionaries, ready to validate, store, or hand to the next step in our pipeline.</p><h4>Conclusion</h4><p>In one quick API call, we managed to turn spreadsheets into rows of objects — exactly what our application likes to digest. No manual parsing required.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=22626342d0db" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Block Disposable Email Addresses at Signup in Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-block-disposable-email-addresses-at-signup-in-python-94206781c38d?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/94206781c38d</guid>
            <category><![CDATA[web-app-development]]></category>
            <category><![CDATA[email-validation]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 16:48:47 GMT</pubDate>
            <atom:updated>2026-11-10T16:48:47.001Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3ULxKKg4SsSJzeBrB76E2A.jpeg" /></figure><p>For any signup page, a way-too-large share of the emails that come in are junk. Think typo’d domains, addresses that don’t resolve, or throwaway inboxes from disposable-mail services that <em>*poof*</em> vanish in ten minutes.</p><p>Let’s avoid regex and instead fix that by adding a quick and easy email validation service to our signup processing workflow. We’ll use the Cloudmersive Validate API’s email_full_validation() method to check an email address’s syntax, domain, mail server, and disposable status in a single call.</p><h4>Installing and configuring the client</h4><p>To install the client, let’s run this command:</p><pre>pip install cloudmersive-validate-api-client</pre><p>And following that, let’s configure the client:</p><pre>import os<br>import cloudmersive_validate_api_client<br><br>config = cloudmersive_validate_api_client.Configuration()<br>config.api_key[&quot;Apikey&quot;] = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>email_api = cloudmersive_validate_api_client.EmailApi(<br>    cloudmersive_validate_api_client.ApiClient(config)<br>)</pre><p>We’ll need to replace “CLOUDMERSIVE_API_KEY” with our actual API key string. To get one of those, we can create a free account on the Cloudmersive website; we’ll get 800 API calls/month with no commitments.</p><h4>Validating an email address</h4><p>Full email validation goes far beyond checking syntax. It’s about confirming the domain actually exists, that the mail server actually accepts mail, and whether the domain is a known disposable provider.</p><pre>def check_email(address):<br>    request = cloudmersive_validate_api_client.FullEmailValidationRequest(email=address)<br>    return email_api.email_full_validation(request)</pre><h4>Deciding at signup</h4><p>Thgouth valid_address, we get syntax, domain, and SMTP in one verdict. We can pair that with is_dispoable to turn away burner inboxes even when they’re technically deliverable.</p><pre>def is_acceptable(address):<br>    result = check_email(address)<br>    return result.valid_address is True and result.is_disposable is not True<br><br>if not is_acceptable(form_email):<br>    reject(&quot;Please use a valid, non-disposable email address.&quot;)</pre><h4>Conclusion</h4><p>Checking deliverability and disposability at signup keeps your user table full of email addresses you can actually reach. That’s far more valuable than a table full of bounces and throwaway accounts (you’ll just be scrubbing those out later, and no one wants to do that).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=94206781c38d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Detect SQL Injection and XSS in User Input with Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-detect-sql-injection-and-xss-in-user-input-with-python-4dd1d99c8b1b?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/4dd1d99c8b1b</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[application-security]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[web-application-security]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 17:41:01 GMT</pubDate>
            <atom:updated>2026-11-09T17:41:01.460Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-N8pEF51VshEXqTq46q3RQ.jpeg" /></figure><p>If you’re handling user input in Python, you laready know the standard defenses: parameterized queries (or an ORM) to neutralize SQL injection, and output encoding + content security policy to shut down XSS. Those are the right tools, and nothing we’ll discuss here replaces them; they stop the attack at the sink.</p><p>But it’s worth noting these defenses stop attacks silently. There’s no built-in threat logging mechanism that comes with implementing “best practices”. Parameterized queries turn a malicious string into a harmless literal and move on; in other words, you never actually learn <em>that </em>you were probed, by <em>whom</em>, or <em>how often</em> that entity was probing.</p><p>That blind spot is where a bona fide threat detection service earns its place. Screening input at the boundary gives you the signal your sink-level defenses throw away: an attempt you can log, rate-limit, alert on, or block before it reaches code paths your escaping doesn’t cover (logs, downstream services, data stored now and rendered somewhere else later).</p><p>Let’s add that advanced detection layer in Python using the Cloudmersive Security API.</p><h4>Installing the client</h4><p>Let’s first run this command to install the client:</p><pre>pip install cloudmersive-security-api-client</pre><h4>Configuring the client</h4><p>Next up, let’s import the resources and configure:</p><pre>import os<br>import cloudmersive_security_api_client<br><br>config = cloudmersive_security_api_client.Configuration()<br>config.api_key[&quot;Apikey&quot;] = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>threat_api = cloudmersive_security_api_client.ContentThreatDetectionApi(<br>    cloudmersive_security_api_client.ApiClient(config)<br>)</pre><p>We can create a connection with a free API key by first creating a free account on the Cloudmersive website.</p><h4>Checking each threat</h4><p>We’ll make exactly one API call per threat type — each reduced to a simple boolean:</p><pre>def has_sql_injection(text):<br>    result = threat_api.content_threat_detection_check_sql_injection_string(text)<br>    return result.contained_sql_injection_attack is True<br><br>def has_xss(text):<br>    result = threat_api.content_threat_detection_protect_xss(text)<br>    return result.contained_xss is True</pre><h4>Screening an input</h4><p>Now we’ll combine them into one pass that reports which threats (if any) it found:</p><pre>def screen_input(text):<br>    threats = []<br>    if has_sql_injection(text):<br>        threats.append(&quot;sql_injection&quot;)<br>    if has_xss(text):<br>        threats.append(&quot;xss&quot;)<br>    return threats<br><br>threats = screen_input(request_field)<br>if threats:<br>    raise ValueError(f&quot;Rejected input: {&#39;, &#39;.join(threats)}&quot;)</pre><p>If you’d rather cover both threats (and several more) in a single request, the API’s automatic threat detection endpoint scans for multiple threat types at once (content_threat_detection_automatic_threat_detection_string()).</p><h4>Conclusion</h4><p>Our own detection logic keeps attacks from landing, but true detection tells you it was attempted. Running this screen at the application’s input boundary turns silent protection into something you can see, log, and respond to.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4dd1d99c8b1b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Verify a File’s Real Type in Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-verify-a-files-real-type-in-python-15eb43438e70?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/15eb43438e70</guid>
            <category><![CDATA[api]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[python-programming]]></category>
            <category><![CDATA[file-upload-vulnerability]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 14:22:20 GMT</pubDate>
            <atom:updated>2026-11-09T14:22:20.081Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Hcem95yVQoerqRAzBH8Upw.jpeg" /></figure><p>Nothing stops someone from renaming payload.exe to invoice.pdf. To actually trust a file’s type, you have to read its contents, not just its name.</p><p>Writing software that reads file bytes is both boring and complicated. It’s also a pain to update. So let’s use an API instead — specifically, the Cloudmersive Convert API, which exposes methods for detecting a file’s real format no matter what the filename claims.</p><h4>Installing the client</h4><p>First things first, let’s install the Cloudmersive client with the below command:</p><pre>pip install cloudmersive-convert-api-client</pre><h4>Configuring the client</h4><p>Next, let’s import and configure the client:</p><pre>import os<br>import cloudmersive_convert_api_client<br><br>config = cloudmersive_convert_api_client.Configuration()<br>config.api_key[&quot;Apikey&quot;] = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>convert_api = cloudmersive_convert_api_client.ConvertDocumentApi(<br>    cloudmersive_convert_api_client.ApiClient(config)<br>)</pre><h4>Detecting the real type</h4><p>The <strong>autodetect </strong>endpoint inspects the file’s contents and returnst he extension it actually matches (complete with the leading period):</p><pre>def detected_extension(path):<br>    info = convert_api.convert_document_autodetect_get_info(path)<br>    return info.detected_file_extension   # e.g. &quot;.pdf&quot;, &quot;.docx&quot;, &quot;.png&quot;</pre><h4>Comparing claimed against actual</h4><p>Now we can simply line up the extension on the filename against the one the file contents reveal. If we get a mismatch, we’re dealing with a spoofed file.</p><pre>import pathlib<br><br>def verify_type(path):<br>    claimed = pathlib.Path(path).suffix.lower()<br>    actual = (detected_extension(path) or &quot;&quot;).lower()<br><br>    return {&quot;claimed&quot;: claimed, &quot;actual&quot;: actual, &quot;matches&quot;: claimed == actual}<br><br>result = verify_type(&quot;uploads/invoice.pdf&quot;)<br>if not result[&quot;matches&quot;]:<br>    print(f&quot;Rejected: claims {result[&#39;claimed&#39;]} but is really {result[&#39;actual&#39;]}&quot;)Conclusion</pre><p>With this logic in place, a genuine PDF passes, while an executable wearing a .pdf suffix comes back as .exe and gets rejected before it can do end up somewhere it shouldn’t.</p><h4>Conclusion</h4><p>File contents are the truth, with the filename is just a claim. Verifying detected file types against claimed extensions holds firm against surface-level spoofing attempts.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=15eb43438e70" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Route Suspicious Documents to a Review Queue in Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-route-suspicious-documents-to-a-review-queue-in-python-adbd9aac4e34?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/adbd9aac4e34</guid>
            <category><![CDATA[api]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[application-security]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Thu, 05 Nov 2026 20:21:14 GMT</pubDate>
            <atom:updated>2026-11-05T20:21:14.172Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GJQtsRghR1qUorOnkxG5fg.jpeg" /></figure><p>File scanners typically give us one of two outcomes: clean, or not clean. But in real document workflows, “not clean” is often too blunt. A password-protected PDf or a macro-enabled spreadsheet will trip a strict scanner, yet both are routine business documents. Blocking them outright frustrates users; waving them through anyway defeats the purpose of the scan.</p><p>If we’re stuck dealing with files like these in our workflow, the best bet is to send the ambiguous files to a human reviewer.</p><p>It’s easy enough to automate that process. We’ll build a three-way router in Python using an Enum for outcomes, a dataclass to carry the decision, and structural pattern matching (Python 3.10+) to dispath on it. As usual, we’ll handle the actual scanning portion of our workflow with the Cloudmersive Advanced Scan API, whose per-category flags let us tell “dangerous” files apart from “work a second look” files.</p><h4>Installing and configuring</h4><p>Let’s first install Cloudmersive:</p><pre>pip install cloudmersive-virus-api-client</pre><p>And then let’s configure our API key:</p><pre>import os<br>import cloudmersive_virus_api_client<br><br>config = cloudmersive_virus_api_client.Configuration()<br>config.api_key[&quot;Apikey&quot;] = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>scan_api = cloudmersive_virus_api_client.ScanApi(<br>    cloudmersive_virus_api_client.ApiClient(config)<br>)</pre><h4>Modeling the decision</h4><p>Now let’s model three outcomes, and build a decision that carries the reasons behind it:</p><pre>from enum import Enum<br>from dataclasses import dataclass, field<br><br>class Outcome(Enum):<br>    CLEAN = &quot;clean&quot;<br>    REVIEW = &quot;review&quot;<br>    BLOCK = &quot;block&quot;<br><br>@dataclass<br>class Decision:<br>    outcome: Outcome<br>    reasons: list[str] = field(default_factory=list)</pre><h4>Triaging a scan result</h4><p>The point is that some findings are unambiguously hostile, while other just warrant a closer look. We’ll split the advanced scan’s flags into those two groups and decide accordingly:</p><pre>HARD_THREATS = {<br>    &quot;contains_executable&quot;: &quot;executable content&quot;,<br>    &quot;contains_script&quot;: &quot;embedded script&quot;,<br>    &quot;contains_insecure_deserialization&quot;: &quot;insecure deserialization&quot;,<br>    &quot;contains_xml_external_entities&quot;: &quot;XML external entity&quot;,<br>}<br>SOFT_FLAGS = {<br>    &quot;contains_macros&quot;: &quot;macros&quot;,<br>    &quot;contains_password_protected_file&quot;: &quot;password protection&quot;,<br>    &quot;contains_ole_embedded_object&quot;: &quot;embedded OLE object&quot;,<br>}<br><br>def triage(result) -&gt; Decision:<br>    if result.found_viruses:<br>        return Decision(Outcome.BLOCK, [v.virus_name for v in result.found_viruses])<br><br>    hard = [label for attr, label in HARD_THREATS.items() if getattr(result, attr)]<br>    if hard:<br>        return Decision(Outcome.BLOCK, hard)<br><br>    if result.clean_result is True:<br>        return Decision(Outcome.CLEAN)<br><br>    soft = [label for attr, label in SOFT_FLAGS.items() if getattr(result, attr)]<br>    return Decision(Outcome.REVIEW, soft or [&quot;flagged, reason unspecified&quot;])</pre><h4>Routing the document</h4><p>Now, the scan runs, triage decides, and match sends the file down the right path:</p><pre>def route(path, review_queue: list[str]) -&gt; Decision:<br>    result = scan_api.scan_file_advanced(<br>        path,<br>        allow_executables=False,<br>        allow_scripts=False,<br>        allow_macros=False,<br>        allow_invalid_files=False,<br>        allow_password_protected_files=False,<br>        allow_unsafe_archives=False,<br>    )<br>    decision = triage(result)<br><br>    match decision.outcome:<br>        case Outcome.CLEAN:<br>            pass                      # hand off to normal processing<br>        case Outcome.REVIEW:<br>            review_queue.append(path) # a human looks before it proceeds<br>        case Outcome.BLOCK:<br>            pass                      # drop it; log decision.reasons<br><br>    return decision</pre><p>Note thatreview_queue is a list here, but in a real-world context it’s the seam where you’d push onto a SQS, a database table, or a Celery task in production.</p><h4>Conclusion</h4><p>Reading the individual scan flags instead of one clean/not-clean verdict lets you keep dangerous files out of your system while giving borderline ones the human judgement they actually need. The triage rules live in one place, so re-drawing the line between block and review is just a one-line change.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=adbd9aac4e34" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Safely Convert User-Uploaded Documents to PDF in Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-safely-convert-user-uploaded-documents-to-pdf-in-python-23da820e2cae?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/23da820e2cae</guid>
            <category><![CDATA[pdf]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[application-security]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 13:44:54 GMT</pubDate>
            <atom:updated>2026-11-04T13:44:54.819Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*so81yO-4wJiJaOLdQcZzaA.jpeg" /></figure><p>You’re accepting document uploads, and you need them all as PDF (for previews, for archiving, or simply to give the rest of your system one predictable format to work with).</p><p>The conversion part is easy; the uncomfortable part is that you’re feeding files<em> you didn’t create</em> into a converter. A malformed or malicious upload is exactly the kind of input you’d rather that converter (or anything in the software chain) never touched.</p><p>So when we talk about “safe” PDF conversion, it boils down to one rule: screen every file before converting it.</p><p>In the below example, we’ll run each upload through the Cloudmersive Virus Scan API before handing clean documents to a PDF conversion API.</p><h4>Installing the clients</h4><pre>pip install cloudmersive-convert-api-client cloudmersive-virus-api-client</pre><h4>Configuring the clients</h4><p>Each of the above packages carries its own configuration, so we’ll set both from the same Cloudmersive API key:</p><pre>import os<br>import cloudmersive_convert_api_client<br>import cloudmersive_virus_api_client<br><br>api_key = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>convert_config = cloudmersive_convert_api_client.Configuration()<br>convert_config.api_key[&quot;Apikey&quot;] = api_key<br><br>scan_config = cloudmersive_virus_api_client.Configuration()<br>scan_config.api_key[&quot;Apikey&quot;] = api_key<br><br>convert_api = cloudmersive_convert_api_client.ConvertDocumentApi(<br>    cloudmersive_convert_api_client.ApiClient(convert_config)<br>)<br>scan_api = cloudmersive_virus_api_client.ScanApi(<br>    cloudmersive_virus_api_client.ApiClient(scan_config)<br>)</pre><h4>Screening a file</h4><p>The advanced scan method from the Virus Scan API rejects the exact type of content we never want near a converter: executables, scripts, macros, malformed fils, unsafe archives, and others. The below helper reduces the API response to a simple “yes” or “no” gate pre-conversion:</p><pre>def passes_screening(path):<br>    result = scan_api.scan_file_advanced(<br>        path,<br>        allow_executables=False,<br>        allow_scripts=False,<br>        allow_macros=False,<br>        allow_invalid_files=False,<br>        allow_password_protected_files=False,<br>        allow_unsafe_archives=False,<br>    )<br>    return result.clean_result is True</pre><h4>Converting to PDF</h4><p>For the conversion itself, the “autodetect and convert” endpoint we’re using figures out the input format on its own, which is useful when users can send Word, Excel, PowerPoint images, and other similarly common file types through the same path. It hands back the new PDF document as bytes:</p><pre>def to_pdf(path):<br>    return convert_api.convert_document_autodetect_to_pdf(path)</pre><h4>Putting it together</h4><p>Now we’ll assemble our code in the order that makes our conversion safe. We’ll screen first, and convert only on a “pass”. A file that fails its threat check never reaches to_pdf:</p><pre>def convert_safely(path, output_path):<br>    if not passes_screening(path):<br>        raise ValueError(f&quot;{path} failed screening; conversion skipped.&quot;)<br><br>    pdf_bytes = to_pdf(path)<br>    with open(output_path, &quot;wb&quot;) as f:<br>        f.write(pdf_bytes)<br><br>convert_safely(&quot;uploads/report.docx&quot;, &quot;converted/report.pdf&quot;)</pre><p>A clean document comes out the other side as a PDF on disk, while a suspicious one raises before the converter is hever called.</p><h4>Conclusion</h4><p>Since our scan gates our conversion directly, the converter only ever works on input we’ve thoroughly vetted. Importantly, the screening and conversion steps also stay independent, so we can tighten the rules or change the output format without disturbing the other half.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=23da820e2cae" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Process a Folder of Documents Without Overloading an API in Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-process-a-folder-of-documents-without-overloading-an-api-in-python-fa10e50e2009?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/fa10e50e2009</guid>
            <category><![CDATA[api]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[python-programming]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Tue, 03 Nov 2026 15:05:48 GMT</pubDate>
            <atom:updated>2026-11-03T15:05:48.673Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hx1jiw-ZrDmFeOiET9F9SA.jpeg" /></figure><p>For a handful of files, looping API calls over a folder works fine. For a few hundred files, however, we’ll hit rate limits or trigger timeouts pretty quickly, and we might run up an unepxected bill. We have to remember that Python lets us fire off requests really fast.</p><p>We don’t need to slow the whole script down to solve the problem. Rather, we can limit how many reqeusts are ever in flight at once.</p><p>Below, we’ll learn how to implement that logic in Python before making API calls to the Cloudmersive Document AI Field Extraction API.</p><h4>Installing the client</h4><pre>pip install cloudmersive-documentai-api-client</pre><h4>Configuring Document AI</h4><pre>import os<br>import cloudmersive_documentai_api_client<br>from cloudmersive_documentai_api_client.rest import ApiException<br><br>config = cloudmersive_documentai_api_client.Configuration()<br>config.api_key[&quot;Apikey&quot;] = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>extract_api = cloudmersive_documentai_api_client.ExtractApi(<br>    cloudmersive_documentai_api_client.ApiClient(config)<br>)<br><br>FIELDS = &quot;Invoice Number,Invoice Date,Total Due&quot;</pre><h4>Extracting from a single file</h4><p>In the below code, we’ll isoltate the work for one document it its own function. Returning errors instead of raising them means a single bad file won’t sink the whole batch:</p><pre>def extract_one(path):<br>    try:<br>        response = extract_api.extract_fields(<br>            field_names=FIELDS,<br>            recognition_mode=&quot;Advanced&quot;,<br>            input_file=str(path),<br>        )<br>        return path.name, {r.field_name: r.field_string_value for r in response.results}<br>    except ApiException as error:<br>        return path.name, {&quot;error&quot;: str(error)}</pre><h4>Capping how many run at once</h4><p>Here, we’ll keep the API from getting overwhelmed. A ThreadPoolExecutor with a small max_workers means only that specific number of requests are ever active at a time, no matter how many files the original folder holds. The moment a slot frees up, the pool picks up the next file.</p><pre>import pathlib<br>from concurrent.futures import ThreadPoolExecutor<br><br>def process_folder(folder, max_workers=4):<br>    files = [p for p in pathlib.Path(folder).iterdir() if p.is_file()]<br><br>    results = {}<br>    with ThreadPoolExecutor(max_workers=max_workers) as pool:<br>        for name, fields in pool.map(extract_one, files):<br>            results[name] = fields<br><br>    return results</pre><h4>Running the batch</h4><pre>summary = process_folder(&quot;./invoices&quot;, max_workers=4)<br><br>for name, fields in summary.items():<br>    print(name, fields)</pre><p>Now four files process concurrently, while the other 496 patiently wait their turn. If the API enforces a strict per-second rate rather than a concurrency limit, we can simply lower max_workers futher, or possibly add a short time.sleep inside extract_one to space the calls out.</p><h4>Conclusion</h4><p>Whether our starting folder holds five documents or five thousand, max_workers is what ultimately decides how hard we lean on the API. Tuning that single number is how we balance throughput against API limits.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fa10e50e2009" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Scan Files Before Uploading Them to Amazon S3 in Python]]></title>
            <link>https://cloudmersive.medium.com/how-to-scan-files-before-uploading-them-to-amazon-s3-in-python-a33ffa5c4e75?source=rss-2f1582e63183------2</link>
            <guid isPermaLink="false">https://medium.com/p/a33ffa5c4e75</guid>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[aws-s3]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[Cloudmersive]]></dc:creator>
            <pubDate>Mon, 02 Nov 2026 19:26:01 GMT</pubDate>
            <atom:updated>2026-11-02T19:26:01.841Z</atom:updated>
            <content:encoded><![CDATA[<p>Our pipeline might push files to S3 and move on, but the upload is rarely the end of their journey. Other systems tend to read files from the buckets we fill; lambdas fire on new objects, nightly jobs process everything inside, and other services download files on demand. If there’s a malicious file sitting in a bucket, it’s a problem for every one of those systems.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vdz-qiE0l2xg4eoE58u-5w.jpeg" /></figure><p>While it’s true that we <em>could </em>scan files with an S3 trigger after upload, that would still let a bad file land in our bucket first — and we don’t want that. Scanning before the upload means the file never arrives at all, and that’s a much safer bet.</p><p>Let’s handle that with a pre-upload gate. We’ll use boto3 and the Cloudmersive Advanced Virus Scan API.</p><h4>Installing the dependencies</h4><pre>pip install boto3 cloudmersive-virus-api-client</pre><h4>Connecting to boto3 and Cloudmersive</h4><pre>import os<br>import boto3<br>import cloudmersive_virus_api_client<br><br>cfg = cloudmersive_virus_api_client.Configuration()<br>cfg.api_key[&quot;Apikey&quot;] = os.environ[&quot;CLOUDMERSIVE_API_KEY&quot;]<br><br>scan_api = cloudmersive_virus_api_client.ScanApi(<br>    cloudmersive_virus_api_client.ApiClient(cfg)<br>)<br><br>s3 = boto3.client(&quot;s3&quot;)<br>BUCKET = &quot;my-upload-bucket&quot;</pre><p>Note that we can get a free Cloudmersive API key by creating a free account on the Cloudmersive website.</p><h4>Building a gate for a single file</h4><p>Since these files already sit on disk, we can scan each file path directly. We’ll just put the whole decision (scan → upload || refuse) into one function that reports back what it did.</p><pre>import pathlib<br><br>def upload_if_clean(path: pathlib.Path) -&gt; str:<br>    verdict = scan_api.scan_file_advanced(<br>        str(path),<br>        allow_executables=False,<br>        allow_scripts=False,<br>        allow_macros=False,<br>        allow_invalid_files=False,<br>        allow_password_protected_files=False,<br>        allow_unsafe_archives=False,<br>    )<br><br>    if verdict.clean_result is not True:<br>        return &quot;blocked&quot;<br><br>    s3.upload_file(str(path), BUCKET, path.name)<br>    return &quot;uploaded&quot;p</pre><h4>Running it over a folder</h4><p>Now that we’ve implemented per-file decision making, processing a directory is a short loop. Each file is responsible for announcing its own outcome:</p><pre>for item in pathlib.Path(&quot;./outbox&quot;).iterdir():<br>    if item.is_file():<br>        print(f&quot;{item.name}: {upload_if_clean(item)}&quot;)</pre><p>A blocked file simply can’t land in the bucket because it never reaches upload_file.</p><h4>Wrapping up</h4><p>Vetting files in this simple, easy way keeps our bucket safe by design instead of relying on a cleanup pass later. The same gate works against any bucket or prefix we aim it at because the scan and transfer stay decoupled.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a33ffa5c4e75" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>