07 Jul 2026
Planet Python
Brett Cannon: How to publish to PyPI using GitHub Actions securely
There have been several security incidents lately that involved compromising GitHub Actions workflows. This has led some to say "GitHub Actions is the weakest link" in publishing and to GitHub publishing a GitHub Actions security roadmap update. But saying it&aposs an issue and acknowledging the fact is one thing, but you still need to do the mitigation work today so you are not going to be the next headline. So this post is going to outline 3 things to do so you can publish to PyPI securely when using GitHub Actions.
But before I go any farther, I want to make 2 things very clear. One is this post is in no way meant to shame anyone into using GitHub Actions. For instance, I have heard people trying to shame maintainers into using GitHub Actions to use Trusted Publishing, and I think that&aposs wrong. Now, if you choose to use a platform that supports Trusted Publishing, then you should definitely use it. But Trusted Publishing is not a reason to change your publishing workflow if the one you have is already secure. In other words, use whatever works best for you to publish securely to PyPI, and if that&aposs GitHub Actions then this blog post is for you.
Two, the title of this post explicitly says "publishing" and not "building and publishing". Doing builds securely is a separate concern that I am not covering. The one piece of advice I will give, though, is one the Python security developer in residence gave me: you should have building and publishing be separate workflows.
With that out of the way, here are 3 steps to securing GitHub Actions for publishing to PyPI that should be relatively painless.
Use zizmor
The zizmor tool examines your GitHub Actions workflows to find things that at dubious when it comes to security. They pretty much all stem from GitHub Actions having insecure defaults in the name of convenience. There are 2 parts to using zizmor:
- Make it happy
- Set it up in CI
You can do those two things in whatever order you want but you need to do both to make sure you fix any current issues you have and prevent any new issues from slipping in. Luckily both things are easy to do.
Make zizmor happy
To run zizmor you can do uvx zizmor --quiet --fix .github/ , pipx zizmor --quiet --fix .github/ , or however you choose to run it. That will run zizmor and fix anything that it can in a clean way. Chances are, though, there will be three things to fix by hand.
No permissions by default
By default, the token GitHub Actions gives to your workflow via GITHUB_TOKEN is way too broad, so zizmor flags it. Easiest way to fix this issue is to turn off all permissions at the global level for a workflow and then turn any permissions you need on at the job level. So put the following at the global level of your workflow file (I personally put it just before jobs:):
permissions: {}
If you happen to need some specific permission, you can then specify it per-job so you scope it as tightly as possible. Or if you really need something for everything, you can still set it globally, but you at least you will be explicit about exactly what you want.
The reason you do this is you don&apost want some action to get a hold of your token that can do something as if you&aposre you and do something bad.
No persisted credentials after checkout
When you use the checkout action, GitHub Actions is running Git on your behalf, complete with credentials so the git checkout command works. The problem is those credentials persist passed the checkout action unless you specifically say to not keep them around. So add the following with: clause to your checkout action:
with:
persist-credentials: false
You do this so your credentials don&apost leak out to some action that will do something bad with them.
Pin your actions
When you specify an action to use in a workflow, you were probably told to use some Git tag like uses: actions/checkout@v7 which specifies using the v7 tag from the https://github.com/actions/checkout repo. The problem with that is if that action gets compromised, an attacker can just update that tag to point to malicious code and so now you&aposre compromised.
You work around this by pinning your actions to commit hashes. This might sound like a massive hassle, but there are tools that can pin all your actions for you.
- gha-update
zizmor --fix --gh-tokenwith a (permissionless) token- Pinact
Those go from simplest to fanciest, but they all get the job done. I personally use gha-update as it&aposs quick and updates my versions along the way. But if you want to keep your current versions as-is then zizmor will do it for you, but you need to give it a token to do the updates (the token is required to avoid being throttled by GitHub). The best thing to do is to use a permissionless token, but if you&aposre being lazy and trust zizmor (and any tool you might be using to run it, e.g. uvx), you can get a token from gh auth token (the following example is for the Fish shell; adjust the syntax for calling gh accordingly for your shell and how you prefer to call zizmor):
zizmor --quiet --fix --gh-token (gh auth token) .github
If you need fancier than any of that, use Pinact.
You also want to require pinning not only for your workflows but any actions that use actions themselves so you&aposre pinned top to bottom. The easiest way to make that a requirement is to run the following command:
gh api "/repos/{owner}/{repo}/actions/permissions" --method PUT --field enabled=true --field sha_pinning_required=true
There&aposs also a way to do it via the UI:
Screenshot of turning on required SHA pinning in a repo under Settings - Actions - General
Bonus: Dependabot to keep actions up-to-date
Dependabot will recognize your use of pins, so you can still use it to keep your actions up-to-date (if you so choose; it&aposs okay if you don&apost want to use Dependabot). The one thing I suggest is using a cooldown so you don&apost accidentally pick to a malicious update by adding a cooldown of a week to your dependabot.yml:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
cooldown:
default-days: 7
Add zizmor to CI
Conveniently, zizmor has an action you can set up in your repo. Using it will cause any issues found to be reported as a code scanning result under the "Security and quality" tab (which can be turned off).
Screenshot showing the "Code scanning" view under the "Security and quality" tab on GitHub
This means the results are private and thus you don&apost have to worry about exposing anything publicly. You can also use the results as a TODO list if you would find that more motivating to have something to check off instead of getting everything working upfront. As well, if you want to do it gradually this will give you a checklist of things to fix.
You can also run zizmor manually if you want in CI, but I personally just use the zizmor action in a dedicated workflow since the zizmor docs provide such a workflow configuration.
Use Trusted Publishing
If you&aposre going to use GitHub Actions to publish to PyPI, I don&apost see any reason not to use Trusted Publishing. It means you don&apost have to manage any API tokens and you can get attestations. Basically it means you get to outsource your security concerns for how you communicate with PyPI for publishing to GitHub&aposs security team.
The one thing you should make sure to do when setting up Trusted Publishing is set up a GitHub environment. The Trusted Publishing docs strongly encourage it and so do I. You can even have the environment do nothing, but doing it now at least gives you an easy option to use it for something later. But I do suggest you use environments to ...
Require approval to publish
The one specific thing I suggest you do with your GitHub environment is require reviewers to run your publishing workflow. The required reviewer can be yourself! But the key point is to require someone to approve the workflow to run.
You might be wondering what&aposs the point if you trigger the release yourself? It&aposs to add a gate to protect against accidental running of your publishing workflow. The accident could be from you or it could be from a malicious actor who has managed to trigger the workflow. By requiring your approval, neither scenario can happen without you clicking that approval button while logged into your GitHub account. And that means someone would need to hack your GitHub account to work around it (and as mentioned above, that means you get to lean on the GitHub security team from preventing that from happening).
Out of everything I have listed, this is probably the most arduous as it&aposs a cost every time you want to do a release. But it&aposs one approval and you&aposre probably already going to be doing something to trigger the release, so you&aposre already online.
And that&aposs it! Those 3 steps get you a long way towards publishing securely from GitHub Actions to PyPI.
Acknowledgments
Thanks to Seth Larson for providing feedback on a draft of this post and giving advice on Mastodon when I posted about these steps. Thanks to William Woodruff for creating zizmor and also giving advice on Mastodon. And thanks to everyone who participated constructively in the discussion on Mastodon.
07 Jul 2026 8:44pm GMT
PyCoder’s Weekly: Issue #742: Wagtail as Admin, Random Values, Code Quality, and More (2026-07-07)
#742 - JULY 7, 2026
View in Browser »
Wagtail as Django Admin on Steroids
Wagtail can do pretty much everything the Django Admin can do, but includes a much more modern UI and more features. This article shows you how to use Wagtail as an Admin alternative.
TIM KAMANIN
Selecting Random Values in Python
Python's random module provides utilities for generating pseudorandom numbers. For cryptographically-secure randomness, use the secrets module instead.
TREY HUNNER
Let AI Agents Into Your B2B App. Securely.
More of your users are asking to connect AI agents to your product, and you want to say yes. PropelAuth lets you give each agent scoped, revocable access, so you stay in control of what it can do. Learn more →
PROPELAUTH sponsor
Managing and Measuring Python Code Quality
Master Python code quality tools like linters, formatters, type checkers, and profilers to measure, manage, and improve the code you write.
REAL PYTHON course
Discussions
Articles & Tutorials
Thinking About Running for the PSF Board? Let's Talk!
The Python Software Foundation Board has announced two office-hour sessions dedicated to giving information on running for the PSF Board. If you're thinking of running in the upcoming election, these sessions can help you understand the ins and outs.
PYTHON SOFTWARE FOUNDATION
Celery on AWS ECS: Complete Guide
Running Celery on AWS ECS without losing tasks and making sure that all the work is done is not as straightforward as it may seem. Learn how to configure Celery and structure your tasks for reliable processing.
JAN GIACOMELLI • Shared by Špela Giacomelli
Learn the Agentic Coding Workflow That Actually Works on Real Projects
65% of Python developers are stuck using AI for small tasks that fall apart on anything real. This 2-day live course (July 11-12 via Zoom) walks you through building a complete Python app with OpenAI's Codex, from an empty directory to a shipped project on GitHub. See the Full Curriculum →
REAL PYTHON sponsor
Free-Threaded Python: Past, Present, and Future
This post summarizes a talk by core developer Thomas Wouters at PyCon US 2026 on Free-threaded Python: the attempt to remove the GIL. It describes why it is being done and what future work looks like.
JAKE EDGE
In Search of a New Contribution Model
This opinion piece from Carlton Gibson, a core Django contributor, talks about the state of contributions to OSS, how AI has made them more complicated, and how some key things are still broken.
CARLTON GIBSON
How to Get TIFF MetaData With Python
The Pillow image library gives you lots of tools for dealing with images. This article teaches you how to extract metadata from TIFF files in a few lines of Python.
MIKE DRISCOLL
Profile First: A 10x Faster Django Test Suite
Bob's Django test suite took 30 seconds. cProfile showed 83% of it was one function: password hashing. Here's how he found the bottleneck and the five-line fix.
BOB BELDERBOS
Python 3.15 Preview: Upgraded JIT Compiler
Learn how the upgraded Python 3.15 JIT compiler speeds up your code with a new tracing frontend, register allocation, and in-place numeric operations.
REAL PYTHON
Store Extra Data for Objects in a WeakKeyDictionary
In several programs Adam has wanted to solve the problem of associating extra data with an object. This article outlines his latest approach.
ADAM JOHNSON
How to Get Started With the GitHub Copilot CLI
Learn how to install, authenticate, and use the GitHub Copilot CLI to plan, write, and review Python code from your terminal with AI agents.
REAL PYTHON
Projects & Code
pytest-tia: Run Only the Tests Your Git Diff Actually Affects
GITHUB.COM/BREADMSA • Shared by BreadWasEaten
purejq: A Pure-Python Implementation of jq
GITHUB.COM/ADAM2GO • Shared by adam2go
Events
Python Atlanta
July 9 to July 10, 2026
MEETUP.COM
PyDelhi User Group Meetup
July 11, 2026
MEETUP.COM
DFW Pythoneers 2nd Saturday Teaching Meeting
July 11, 2026
MEETUP.COM
EuroPython 2026
July 13 to July 20, 2026
EUROPYTHON.EU
SciPy 2026
July 13 to July 20, 2026
SCIPY.ORG
EuroSciPy 2026
July 18 to July 24, 2026
EUROSCIPY.ORG
Happy Pythoning!
This was PyCoder's Weekly Issue #742.
View in Browser »
[ Subscribe to 🐍 PyCoder's Weekly 💌 - Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]
07 Jul 2026 7:30pm GMT
PyCharm: Best Object Detection Models for Machine Learning in 2026
Object detection powers transformative applications, from autonomous vehicles navigating city streets and security systems identifying threats in real time to retail analytics tracking inventory and medical imaging detecting tumors. But choosing the right model for your computer vision project can be challenging, especially with dozens of architectures claiming superiority across different metrics.
In this guide, we'll examine the top object detection models available in 2026, comparing their architectures, performance characteristics, and ideal use cases to help you determine which models are best suited to your applications.
Whether you're building real-time video analytics, high-precision inspection systems, or resource-constrained edge applications, you'll find clear guidance on which model best fits your requirements.
What is object detection?
Object detection aims to identify and localize multiple objects within images or video frames. Unlike image classification, which only classifies the broad identity of an image, object detection identifies the objects in an image/video frame and their exact positions within it.
In a nutshell, object detection solves two interdependent problems:
- Localizing (detecting) the objects on the image, by drawing the bounding boxes for the objects on the image (it is possible that there are zero objects!). A bounding box is usually defined as a tuple (x, y, h, w), where x and y are the top-left coordinates of the bounding box rectangle, and h and w are the height and width of the bounding box, respectively.
- Classifying the identities of these images (like a person, car, or dog).
This dual capability makes object detection more complex than classification alone, requiring models that can handle multiple objects of different sizes appearing anywhere in an image.
As with classification tasks, a simple accuracy metric is not sufficient to assess model performance. We need metrics of two types. Firstly, performance metrics that gauge the trade-off between incorrectly detecting objects (false positives) and not detecting objects at all in the image when they were present (false negatives). Secondly, we also need metrics to assess how long it will take our model to perform the task in question: We will call these compute efficiency metrics. Usually, the new architectures for object detection are benchmarked on the validation partition of the COCO dataset and run on T4 NVIDIA GPU hardware.
Here are the standard metrics used in the object detection community:
- Basic building block of performance metric: Intersection over union (IoU) is the foundational geometric measure used to decide whether a predicted bounding box is correct. It is calculated as the area of overlap between the predicted box and the ground-truth box, divided by the area of their union - producing a score between 0 (no overlap) and 1 (perfect match). A detection is counted as a true positive only if its IoU with the nearest ground-truth box exceeds a chosen threshold (e.g. 0.5). A low IoU threshold is lenient about box placement; a high one demands tight localization.
- Performance metric: Mean average precision (mAP), which evaluates detection accuracy by measuring how well predicted boxes overlap with ground truth annotations across different confidence thresholds. The most commonly cited variant, mAP@[50:95] (also written AP50:95), averages precision over IoU thresholds from 0.50 to 0.95 in steps of 0.05, which is a stringent measure that penalizes imprecise localization as much as missed detections.
- mAP50 vs. mAP50:95: mAP50 measures detection at IoU ≥ 0.5 and scores appear higher, favoring faster models. mAP50:95 averages across IoU thresholds 0.5-0.95 - the stricter, preferred metric. For precision-critical applications (robotics, medical), it is common to optimize for mAP50:95.
- Compute efficiency metric: Frames per second (FPS), which measures inference speed, determining whether a model can process video in real-time. For standard videos, real-time is defined as >= 30 FPS (Google or original YOLO paper) or latency <= 33.3ms (1/FPS * 1000). Naturally, for such applications as self-driving cars or robotics, there are higher requirements on the FPS rate, going up as high as 60-100+ FPS.
- Compute efficiency metric: Parameter count is a quality of the model that influences its performance. There is a trade-off between the model's accuracy and its parameter count. That's why models are provided in different sizes of the same architecture (S, M, L, XL, etc.) to cater to various scenarios of this trade-off. This is similar to the concept of parameter count in LLMs.
There are a few popular choices of datasets to evaluate the performance of object detection models. As mentioned above, the standard choice for benchmarking object detection is the COCO dataset, containing 80 object categories across 330,000 images. Naturally, there are a lot of other datasets, specialized for certain domains, such as self-driving cars, or certain scenarios, such as the detection of objects in cluttered environments. What is important to remember is that the values of object detection metrics, IoU, and mAP depend on the dataset they were evaluated on, so mAP@[50:95]=60.1 on the COCO dataset may not be directly transferable to your custom dataset. These metrics should always be re-evaluated on your dataset to define the baseline performance of the models on it.
Object detection algorithms and architecture families
Object detection models fall into two different processing flows and two different architectural families.
Architectures
CNN-based
Examples: Faster R-CNN, Mask R-CNN, Cascade R-CNN, YOLO
CNN-based detectors rely on convolutional layers to extract local features hierarchically across the image, traditionally using predefined anchor boxes as spatial priors for localizing objects. Spatial priors are predefined assumptions about where and what size objects are likely to appear in an image, giving the model a starting point for detection rather than searching randomly.
Transformer-based
Examples: RF-DETR, RT-DETR, D-FINE
Transformer-based detectors, inspired by advances in natural language processing, instead apply global self-attention mechanisms that allow the model to reason about relationships across the entire image simultaneously.
Specifically, transformer-based detectors use learned object queries and global self-attention, where each query is trained to correspond to at most one object, unlike CNN-based detectors, which build spatial understanding locally through convolutional layers with limited receptive fields.
However, in modern architectures, there exists a fusion of the two architectures: a CNN network can use self-attention modules in its architecture, such as YOLOv12 or YOLOv13, leading to cross-architectural designs.
Processing flows
Two-stage detectors
Examples: Faster R-CNN, Mask R-CNN, Cascade R-CNN
The network makes two sequential passes, each with a distinct job:
- Stage 1: Region Proposal:
- Scans the image and proposes ~1000-2000 candidate regions (RoIs) that might contain objects.
- Doesn't care about class yet, just the fact that "something interesting is here".
- This is the region proposal network (RPN) in classic detectors
- Stage 2: RoI classification and refinement:
- Takes only the proposed regions from Stage 1.
- Crops/pools features for each region.
- Predicts the exact class and refined box coordinates for each proposal.
Single-stage detectors
Examples: YOLO series, SSD, RetinaNet, DETR
The network directly predicts class labels and bounding boxes from feature maps. It does everything in one forward pass. Usually, the following happens:
- A dense grid of anchor boxes (or points) is placed over the image.
- For each anchor, the network simultaneously predicts:
- Whether there is an object there (objectness/class score).
- How the box should be adjusted (box regression offsets).
- In older versions of single-stage detectors, one needed to filter overlapping bounding boxes at the end; it was done using the non-maximum suppression (NMS) algorithm. From YOLOv10 on, using NMS is a redundant step.
Furthermore, modern single-stage detectors have moved away from anchor-based designs entirely, predicting box coordinates directly from grid points and pixels, eliminating the need for dataset-specific anchor tuning altogether.
Historically, two-stage detectors offered better accuracy at the cost of speed, but modern single-stage detectors have largely closed this gap, achieving comparable or superior results while remaining significantly faster. Thus, we will focus on single-stage detectors only when evaluating the state-of-the-art models for practical applications.
Top object detection models in 2026
Two-stage pipelines (Faster R-CNN, Mask R-CNN) are no longer competitive. The current frontier is defined by single-stage NMS-free transformer architectures and models of the YOLO family. Each model below excels in a specific deployment scenario.
RF-DETR (by Roboflow) - Highest Accuracy
Real-Time Detection Transformer · ICLR 2026
| Metric | Value |
|---|---|
| mAP50:95 (N) | 48.4 |
| mAP50:95 (M) | 54.7 |
| Latency (N) | 2.3 ms |
| Latency (M) | 4.4 ms |
| mAP50:95 (2XL) | 60.1 (COCO record) |
| Latency (2XL) | 21.8 ms |
The strongest real-time model available. RF-DETR uses DINOv2 to extract deeply rich, globally-aware feature representations of the input image, then uses deformable cross-attention in the detection head to efficiently query those features and predict bounding boxes without needing anchor boxes or NMS post-processing. The result is a model that's simultaneously more accurate on complex scenes and faster at inference than the naive combination of those components would suggest. RF-DETR is the first real-time detector to break 60 mAP on MS COCO. Designed from the ground up for fine-tuning, DINOv2 pre-training on internet-scale data gives it unmatched domain adaptability across aerial imagery, medical scans, industrial inspection, and more. It comes in four sizes: Nano, Small, Medium, Large (plus XL/2XL under a PML license).
Strengths:
- Highest mAP of any real-time model.
- Exceptional domain transfer (fine-tunes fast).
- Best on occluded and complex scenes.
- Supports detection + segmentation in a single API.
- Apache 2.0, fully commercial-friendly.
Limitations:
- Heavier than YOLO on edge/mobile.
- XL/2XL models require a PML license.
- Higher GPU memory vs. YOLO variants.
License: Apache 2.0 (N/S/M/L) · PML 1.0 (XL/2XL)
Repository: https://github.com/roboflow/rf-detr
YOLO12 (Tsinghua University) - Research / Benchmark
Attention-centric YOLO · NeurIPS 2025
| Metric | Value |
|---|---|
| mAP50:95 (N) | 40.4 |
| mAP50:95 (M) | 52.5 |
| Latency (N) | 1.60 ms |
| Latency (M) | 4.27 ms |
| License | AGPL-3.0 |
YOLO12 is the first YOLO model to place attention mechanisms at the core rather than CNNs, matching CNN-based inference speeds while gaining the global context benefits of self-attention. Key innovations: Area attention (A²) divides feature maps into regions to reduce the quadratic cost of full self-attention; Residual ELAN (R-ELAN) stabilizes training of large attention blocks; FlashAttention reduces memory bottlenecks. It is deployable on NVIDIA Jetson, NVIDIA GPUs, and macOS.
A note on implementations. YOLO12 exists in two separate codebases, and the distinction matters in practice. The original authors (Tsinghua/University at Buffalo) actively maintain their own repository at sunsmarterjie/yolov12. In June 2025, they explicitly warned against using Ultralytics' integration, stating it "is inefficient, requires more memory, and has unstable training" - issues they have fixed in their own repo. The training instability and memory criticisms often cited against YOLO12 are therefore criticisms of the Ultralytics port, not the model itself. Ultralytics' recommendation to prefer YOLO26 over YOLO12 should be read with this context in mind: The comparison is partly against their own suboptimal implementation.
If you use YOLO12, install from the original repository rather than via pip install ultralytics.
Strengths:
- Strong accuracy at the nano scale (beats YOLO11-N by 0.9% mAP).
- Long-range context via attention mechanisms: It can take into account the entire image when detecting an object, rather than a local pixel neighborhood, as in pure CNN architectures.
- Jetson-, Android-, and macOS-deployable.
- Original repo fixes memory and training stability issues present in the Ultralytics port.
- Actively maintained by original authors with ongoing updates (turbo variant, segmentation, and classification).
Limitations:
- If using Ultralytics implementation:
- AGPL-3.0 commercial use requires an enterprise license.
- Training instability and high memory on large models.
- If using an open-source implementation:
- AGPL-3.0 commercial use requires an enterprise license.
- Claims to have stable training and inference in comparison with Ulitralytics implementation.
- Requires installing from the original repo to avoid Ultralytics port issues, resulting in slightly more setup friction.
- Smaller ecosystem and community support than Ultralytics-native models.
License: AGPL-3.0 (open-source) · Enterprise license via Ultralytics for commercial use
Open-source repository: https://github.com/sunsmarterjie/yolov12
Ultralytics repository: https://github.com/ultralytics/ultralytics
YOLO26 (Ultralytics) - Best for edge / production
Edge-first unified YOLO · September 2025
| Metric | Value |
|---|---|
| mAP50:95 range | 40.9-57.5 |
| Latency range | 1.7-11.8 ms |
| CPU gain vs. YOLO11 (nano) | +43% |
| Unified tasks | 5 |
Ultralytics' flagship for 2025-2026. YOLO26 shifts focus from accuracy maximization toward deployment-oriented simplification: It removes NMS and distribution focal loss (DFL) for end-to-end inference, introduces the MuSGD optimizer for stable convergence, and adds progressive loss balancing (ProgLoss), which makes sure that the model doesn't over-optimize one objective at the expense of others, and small-target-aware label assignment (STAL), which ensures extra attention to small objects. Five tasks are solved by this one YOLO26: detection, segmentation, pose estimation, oriented bounding boxes detection, and open-vocabulary detection and segmentation. It is explicitly designed for NVIDIA Jetson Orin/Xavier, Qualcomm Snapdragon AI, and ARM CPUs. Supports INT8 and FP16 quantization, plus ONNX, TensorRT, CoreML, and TFLite export.
Strengths:
- Best edge and mobile performance (Jetson Orin and Snapdragon).
- NMS-free leads to lower latency.
- 43% faster CPU inference than YOLO11(N) at comparable accuracy, ideal for devices without a GPU.
- Five tasks in one architecture.
- Stable INT8/FP16 quantization.
Limitations:
- AGPL-3.0: commercial use requires an enterprise license.
- Lower peak accuracy than RF-DETR XL.
License: AGPL-3.0 (open-source) · Enterprise license via Ultralytics for commercial/industrial use.
Repository: https://github.com/ultralytics/ultralytics
Benchmark comparison
To give a comparison between the models, here are the exact benchmark values. All scores on MS COCO val2017. Latency was measured on an NVIDIA T4 GPU.
| Model | mAP50 | mAP50:95 | Latency | Params | Edge-ready | License |
|---|---|---|---|---|---|---|
| RF-DETR-N | 67.6 | 48.4 | 2.3 ms | 30.5 M | Server GPU | Apache 2.0 |
| RF-DETR-M | 73.6 | 54.7 | 4.4 ms | 33.7 M | Server GPU | Apache 2.0 |
| RF-DETR-2XL | 78.5 | 60.1 | 17.2 ms | 126.9 M | Server GPU | PML 1.0 |
| YOLO12-N | 56.7 | 40.4 | 1.6 ms | 2.5 M | ARM / Mobile / Jetson | AGPL-3.0 |
| YOLO12-L | 70.7 | 53.8 | 5.83 ms | 26.5 M | Jetson / TensorRT | AGPL-3.0 |
| YOLO26-N | - | 40.1 | 1.7 ms | 2.4 M | ARM / Mobile / Jetson | AGPL-3.0 |
| YOLO26-X | - | 56.9 | 11.8 ms | 55.7 M | Jetson / TensorRT | AGPL-3.0 |
Here is a visualization of the above results alongside additional modern object detection models for a more holistic comparison:

Use-case guidance
Occluded objects: RF-DETR (M/L) is the clear choice. Its DINOv2 backbone models global context across the full image, making it significantly better than CNN-based models at finding partially hidden objects.
Small objects: RF-DETR uses multi-scale feature extraction. YOLO26 also includes STAL (small-target-aware label assignment), making it competitive for small objects on edge hardware.
Edge / mobile / Jetson: YOLO26-N or YOLO12-N. YOLO26 is the Ultralytics recommendation for Jetson Orin/Xavier, Snapdragon AI, and ARM CPUs. It has 43% faster CPU inference than YOLO11n at comparable accuracy.
Custom domain / fine-tuning: RF-DETR by a significant margin. DINOv2 pre-training means it adapts to new domains (medical, aerial, and industrial) faster and with less data than any other model here.
Licensing Summary
| Model | License | Commercial use |
|---|---|---|
| RF-DETR (base) | Apache 2.0 | Free for all uses, including commercial products |
| RF-DETR XL/2XL | PML 1.0 | Contact Roboflow for commercial licensing |
| YOLO12 | AGPL-3.0 | Free for open source / personal use; commercial applications require an Ultralytics Enterprise license |
| YOLO26 | AGPL-3.0 | Free for open source / personal use; commercial applications require an Ultralytics Enterprise license |
Quick-start code
RF-DETR
# Install
pip install rfdetr
# Inference
from rfdetr import RFDETRBase
model = RFDETRBase()
detections = model.predict("image.jpg")
# Fine-tune on your dataset
model.train(dataset_dir="./my_dataset", epochs=50, batch_size=4)
YOLO26 / YOLO12 (via Ultralytics)
# Install
pip install ultralytics
# Inference - YOLO26
from ultralytics import YOLO
model = YOLO("yolo26n.pt") # or yolo26s/m/l/x
results = model.predict("image.jpg")
# Inference - YOLO12
model = YOLO("yolo12n.pt")
results = model.predict("image.jpg")
# Export for edge (TensorRT / CoreML / ONNX)
model.export(format="engine") # TensorRT for Jetson
model.export(format="coreml") # Apple Silicon / iOS
model.export(format="tflite") # Android / ARM
YOLO12 (use original open-source repo - not the Ultralytics integration)
# Install from the original authors' repo
conda create -n yolov12 python=3.11
conda activate yolov12
git clone https://github.com/sunsmarterjie/yolov12 && cd yolov12
pip install -r requirements.txt
pip install -e .
# Inference
from ultralytics import YOLO
model = YOLO("yolov12n.pt") # or s/m/l/x
results = model("path/to/image.jpg")
results[0].show()
# Export for edge
model.export(format="engine", half=True) # TensorRT FP16
model.export(format="onnx") # ONNX for broad compatibilityTransfer learning and fine-tuning
RF-DETR - recommended for domain shift. Thanks to a DINOv2 backbone that is pre-trained on internet-scale data, fine-tuning requires less labeled data and converges faster. Use the rfdetr package with a COCO pre-trained checkpoint. Roboflow also offers a hosted fine-tuning UI.
YOLO26 / YOLO12 - easiest pipeline. Ultralytics' training API is the most mature fine-tuning ecosystem. It supports YOLO-format and COCO-format datasets and has good documentation and an active community.
# Fine-tuning YOLO26 on a custom dataset (YOLO format)
from ultralytics import YOLO
model = YOLO("yolo26m.pt") # start from pretrained weights
model.train(
data="custom_dataset.yaml", # path to your dataset config
epochs=100,
imgsz=640,
batch=16,
device=0, # GPU index; "cpu" for CPU
)
metrics = model.val() # evaluate on validation setSummary: Choosing the right model for your project
Selecting an object detection model requires matching your specific requirements against each model's strengths. The decision framework below maps common scenarios to optimal model choices.
| Your goal | Best choice | Runner-up |
|---|---|---|
| Highest accuracy, cloud deployment | RF-DETR M/XL | YOLO26-X |
| Edge / Jetson / mobile | YOLO26-N/S | YOLO12-N |
| Fine-tuning on a custom domain | RF-DETR | YOLO26 |
| Occluded / complex scenes | RF-DETR | YOLO26 |
| Research / benchmarking | YOLO12 | RF-DETR |
| Apache 2.0 + commercial use | RF-DETR (base) | YOLO26 |
| Multi-task (detect + segment + pose) | YOLO26 | RF-DETR (det+seg) |
Get started with PyCharm today
Selecting an object detection architecture in 2026 is a strategic decision dictated by the specific requirements of the application and the available computational budget. Whether prioritizing the record-breaking accuracy of RF-DETR for complex scenes or the unmatched efficiency of the YOLO family for edge deployment, the choice must balance mAP requirements against real-time latency constraints.
The landscape of computer vision is rapidly shifting toward zero-shot detection frameworks that recognize novel objects without task-specific supervision. As foundation models increasingly integrate sophisticated image embedders like CLIP or DINOv2 into detection pipelines, the boundaries of high-precision detection on resource-constrained hardware will continue to expand. While transformer-based architectures are developing quickly, the YOLO family's established ecosystem ensures it remains a cornerstone for real-time production environments.
To achieve the best results for your specific use case, we encourage you to experiment with the models and code samples provided in this guide. To that end, PyCharm provides the perfect ecosystem for experimentation with various open-source models via Code -> Insert HF Model interface. If you'd like to try this yourself, PyCharm Pro comes with a 30-day trial.
For a hands-on starting point, this tutorial shows how to build a live object detection app using TensorFlow and PyCharm Jupyter notebooks, then deploy it on a robot - covering everything from single-frame inference to a live web dashboard with annotated detections. Moreover, stay tuned for the next tutorial post, where we will discuss all three object detection models in action.
07 Jul 2026 5:51pm GMT
03 Jul 2026
Django community aggregator: Community blog posts
Issue 344: Happy Birthday Djangonaut Space!
03 Jul 2026 3:00pm GMT
02 Jul 2026
Django community aggregator: Community blog posts
Python Leiden (NL) meetup summaries
Two summaries of the July 2 2026 Python meetup in Leiden. I've omitted one, "Python with Karel" by EiEi Tun, as I've made a summary of that talk in Utrecht a month ago, already :-)
Building modern internal team CLIs with incremental automation - Farid Nouri Neshat
Obligatory xkcd cartoons: https://xkcd.com/974 and https://xkcd.com/1319 and https://xkcd.com/1205
Toil: manual, repetitive, automatable, distracting you from your real work, no enduring value. Yes, he likes to automate things :-) Some examples of repetitive manual tasks:
- Creating dev containers.
- Gathering data for troubleshooting.
- Something that needs to be set manually in a database.
- Setting up a new AWS account.
- Creating a new dev environment on the new colleague's laptop.
How to automate? Do it iteratively! Your boss might not like you to spend a day automating the task. But if you do it small steps at a time...
-
Do it manually the very first time.
-
Then start with documenting the steps.
-
Then turn it into a do-nothing scaffold script:
def step1(): print("Open the AWS page manually") input("Press enter to continue") -
Everytime you do the task, automate a small bit and flesh out the script over time.
-
After many iterations, you'll have automated it fully!
"I don't have time to automate it", you might say? Well, why don't you have time? Is it perhaps because you haven't automated things?
A good motivator: if you hate the task... Hate driven development :-)
After a while, you'll have lots of random scripts. Stuff them in a repository. Slowly document them. Try to get them to use the same conventions. Perhaps you can re-use functionality in a library.
Something you need quicky is some CLI, a command line interface. He likes typer to make his CLIs: much nicer than Python's own "argparse":
import typer
app = typer.Typer()
@app.command()
def hello(name: str):
print(f"Hello {name}")
if __name__ == "__main__":
app()
AI comment: AI agents can use your CLI. Use the docstring and help functions to help orient the AI to your custom CLI. You can, for instance, use a CLI to give the agent access to your database's content without giving it direct access to the database.
AI agents can be dangerous. A solution might be to use "feature flags". You can disable production access until you enable some setting or flag that AI doesn't know about.
He also mentioned the rich library for formatting and colorizing your textual output.
What I've learned maintaining the MCP Python SDK - Marcelo Trylesinski
He's one of the three maintainers of the MCP Python SDK. SDK = software development kit. MCP: model context protocol, so a way for AI agents to connect to some other piece of software.
MCP is basically "OpenAPI for your agents". It exposes three things from the server side:
- tools
- resources
- prompts (though tools are mostly the only thing that is used)
The client provides:
- sampling
- elicitation (="producing a reaction", so mostly it means that the AI server asks you questions)
- roots
- logging
The MCP spec kept growing. But clients never caught up, so it was mostly only the "tools" part that got used.
A big problem is that servers cannot scale. The AI server might have lots of machines with a loadbalancer in front of it, but as a user you need to stay connected to the one machine that has your context.
There's a new version of the spec (final version this month) that actually removed stuff, instead of growing. The "client provides" list mentioned above? Sampling, roots and logging are gone as they were hardly used.
MCP is now a small core, with optional extensions. Examples: tasks, MCP apps, enterprise auth.
The MCP Python SDK supports the new version, too. He demonstrated a small Python script that had a function that said you could have three bananas. He connected it via MCP to Claude and could ask Claude for the number of available bananas. It got back, via the Python tool, with the correct answer.
02 Jul 2026 4:00am GMT
01 Jul 2026
Django community aggregator: Community blog posts
Weeknotes (2026 week 27)
Weeknotes (2026 week 27)
The last entry in this series was published 10 weeks ago so it really is time for another review of the releases I did during this time.
Releases
feincms3-forms
The feincms3-forms forms builder has gained a documentation page on the wonderful Read the Docs service. The 0.6.1 release doesn't contain any code changes, just pyproject.toml updates and the mentioned documentation rework.
django-imagefield
django-imagefield 0.23 is still in alpha. The handling of image fields when using libvips is optimized to use less memory hopefully. We'll see. I also added some tests to verify that .mpo files are handled properly.
feincms3
The Vimeo embed now always sets the dnt=1 parameter on the <iframe>, which asks Vimeo to not track the user.
django-mptt
I wrote about the somewhat annoying maintenance again. The library is still officially unmaintained, but I did a lot of work either just closing issues or also fixing them. The docs also contain many clarifications. I only released 0.19rc1 for now.
feincms3-sites and feincms3-language-sites
Last time I mentioned that default HTTP/S ports are now stripped so that the host matching can determine the correct site. Now a new case appeared where trailing dots weren't stripped. The normalization of hosts has been extended. I'm sure we're still missing some exotic cases where we should do more normalization, but we'll cross that bridge when we get there.
django-prose-editor and django-js-asset
Various upgrades to the editor and especially the importmaps rework in both packages - the importmap infrastructure should now be CSP-compatible! I wrote more about that in the last post The 2026 way of using importmaps in Django.
django-content-editor
Minor bugfixes and a major version bump because of the rework of the JavaScript code into multiple ES modules. The content editor now uses importmaps as well.
django-fhadmin
Small bugfix so that links aren't underlined in the app groups list when they shouldn't be, matching how the Django admin itself behaves.
django-cabinet
The cabinet / prose editor integration for the file (or image) picker is final and released as a stable version.
django-json-schema-editor
This small release only contains more correct German translations of strings.
Honorable mention: django-debug-toolbar
I didn't actually create this release, but I contributed various changes to it. The changelog for 7.0 is here.
01 Jul 2026 5:00pm GMT
23 Jun 2026
Planet Twisted
Glyph Lefkowitz: Adversarial Communication
As I have discussed in previous posts, "AIs" can make mistakes. In fact, they do make mistakes, and their mistake-making patterns are such that where and how they will make mistakes is both uncertain and constantly changing.
Thus, in any scenario where you want to attempt to make "productive" use of "AI", you must have a system in place for checking every result. Not checking some results; checking every result. If each result might have a consequence for you (and if it didn't have a consequence, why bother automating it?) and you cannot predict in advance which kinds of results will need verification, then verification is always required.
The verification often ends up being just as expensive as doing the work in the first place, which means that if you want your usage of "AI" to be personally profitable, you have to find someone else to externalize the cost of verification onto. This person becomes your adversary, and, if you are successful, your "AI's" victim.
The Ladder-Climber And Their Reverse-Centaur Rungs
One way that this constellation of facts can straightforwardly assemble themselves into a dystopian nightmare is the phenomenon, described by Cory Doctorow, of the reverse centaur. This is when your employer non-consensually turns you into the verification system. The "AI" does the fun part of initially performing the work, and then you do the boring part where you check if the robot is right and clean up its messes, even if everyone already knows that it would, in aggregate, be cheaper for you to do the work in the first place.
Reverse centaurs can be made from any automation, not only "AI" automation. I think that there is a reason that this term happens to have emerged in the "age of AI", though, and not with earlier automation technologies (even those which were considerably more viscerally horrific). That reason is: the wrongness of "AI" output is not merely a technical feature that must be compensated for, it is a generalized externality.
As I mentioned above, if you are responsible for the entirety of the work, both extruding the "AI" output and checking it, it's usually cheaper to have humans do the entirety of the work to begin with. When humans do the writing directly, we can check as we go, and thus verification doesn't need to be as comprehensive.
When "AI" coding advocates say "code review is the bottleneck", what they are observing is that the LLM is still rolling the dice for each PR, and a human is still necessary to verify that each of those rolls is a winner. But calling this process "code review" is a bit of a misnomer; it's not really "code review" in the traditional sense, it's human understanding.
Before the advent of "AI", the human understanding was implicit in the process of writing the code in the first place1, and the code review was a way of diffusing and extending that understanding. Now that the code can be authored with no initial understanding taking place, that cost has not gone away, it has moved.
Human understanding was always the bottleneck.
However, this is taking a collaborative view of a software project, where satisfying the needs and solving the problems of your customers are the goals. We can see that "AI" is a bad tool to satisfy those goals, because all it's doing is converting the first half of the work, that of understanding the code as you write it, to understanding the agent's output as you read it.
What if, instead, we were to take the view that every software company is a Hobbesian nightmare, red in tooth and claw? In this view, the only goal of a software project is for the individual developers to make their promo cycles and get their bonuses. Given that there is only a certain amount of money to go around, this is a zero-sum game where each programmer wants to look more productive than their colleagues.
Pretty much every organization finds it easy to reward "productivity" as expressed by lines of code emitted, but the benefits of doing thorough and thoughtful design, analysis, and code review very difficult to reward. In this world, an LLM is an invaluable tool for the sociopathic ladder-climber, particularly if your legacy organization is still structuring their workflows as if the person prompting the bot is "writing" the code, and then they get to foist off the act of "reviewing" the code onto someone else.
Here, the prompter effectively externalizes the cost of the LLM's failures but internalizes any benefits. The prompter will vibe-code a big feature, so large that the assigned reviewer can't possibly comprehend it all effectively. When this happens, the reviewer will, eventually, be pressured to approve it, even if they can try to spot a few problems along the way. The reviewer has their own work to get back to, after all, the obligation to review the prompter's (read: the bot's) code is a drain on their time that they are not going to get rewarded for.
If this feature is a big success, the prompter gets a promotion. If it causes a big issue, well, the reviewer must not have been careful enough.
This is why LLMs are "good for coding", and also why their biggest promoters keep having outages.
The Generative Gish Galloper
Coding is the biggest "success story" of this type of adversarial communication, but it is by far not the only instance of such a thing. LLMs create a new form of leverage that can turn Brandolini's law from a linear advantage into an exponential one. If you are engaged in a political debate where you want to overwhelm the other side in nonsense, an LLM can generate bullshit faster than it is physically possible for a human being to type, let alone respond thoughtfully. There is an asymmetry to the utility of this weapon as well: only one side of the political spectrum wants to flood the zone and destroy trust in institutions and the concept of truth. There's a good reason that the fascists love it.
Straightforward Spam and Fraud
This is kind of obvious, but LLMs can generate lightly-customized, plausible-looking text much more quickly than any human being. This facilitates their use in fraud, spam, and scams. In a spamming or fraudulent interaction, once again, the costs are externalized onto the victim: the recipient of a spam message has to do all the work of "checking" the LLM's output. Spammers already expect very low hit rates from boilerplate, and if the LLM can increase those percentages from 1% to 5% the technology will pay for itself; they don't need anything like reliable accuracy.
Customer "Support"
If you have any kind of commercial relationship with a company, I probably don't even need to mention this: customer "support" bots are a misery. Everybody knows it at this point. But customer support is usually conceptualized by businesses as an adversarial interaction, because it is a cost center. They maintain internal metrics on time-to-resolution and try to optimize them. Implicitly, this creates a dynamic where the goal of the customer service agent's job is not to solve your problem, but to emit noise that will cause you to think your problem is resolved, or to give up, as fast as possible. Unsurprisingly, LLMs can emit this noise faster than humans can, getting those customers off the phone. But those customers will remember those interactions, and the story outside the TTR metrics is horrible.
Similarly to the situation in software development, LLMs can look very good on paper for customer support, but mostly what they are doing is illuminating the problems with the industry's existing metrics, by turning "winning the metrics battle against the customer" into a more obvious and immediate defeat for the company's long term reputation.
"Education"
In 2026 it is sadly a fact of life that students cheat all the time using "AI", and that this cheating is very successful, in that the teachers find it very hard to detect.
LLMs are great for cheating on schoolwork because the student is externalizing the work of the checking onto the teachers, who are often starting at a disadvantage to begin with, at least in the US.
My view is that this is happening because of a divergence in the way that students vs. teachers (or, more accurately, "the broader educational system") view grading.
When a student is asked to write an essay, the teachers see the effort as both intrinsically worthwhile for the student, as well as useful as a pedagogical tool to evaluate and react to the student's progress. The student, by contrast, sees a stumbling block designed to knock them off the path to success and into a permanent underclass. It is no wonder that the student sees "AI" as useful to their own goals and has no compunction about deploying it.
There is a bitter irony that the ability to understand the inherent value of actually writing the essay on their own is the sort of thing that students can really only learn by writing a bunch of essays. There's no way that I can think of which makes the benefit legible as long as a shortcut is available.
The net effect here is a downward spiral, where the already-wobbling educational system is sustaining an attack that it doesn't have the resources to recover from. The individual students' attacks against their teachers and their schools' grading systems might appear to momentarily succeed, but they will win the battle and lose the war.
Spamming "For Good"?
Usually when we talk about someone unilaterally choosing to enter into an adversarial relationship, that's an "attack" and for good reasons we have a negative impression of the attacker. However, I would be remiss if I did not point out that there are some cases where the relationship was already adversarial; just because you're the attacker doesn't mean that you are evil.
For example we might imagine use-cases like automatically filing appeals for prior authorizations against health insurance. It's relatively well-known at this point that the main way for-profit insurers maintain their margins is by denying claims right up to the line of the policies themselves being fraud, so using a spamming tool to fight them might be entirely justifiable2 in that case.
Similarly, using an LLM could be justified in a fight against a company refusing to honor a warranty. One could imagine using an LLM to immediately generate replies and escalations.
However, even in imagined cases like these, the underlying problem is that the insurers and the vendors already have a tremendous amount of structural power, so it is more likely that they will have the advantage in deploying a communications weapon like an LLM, as well as enacting policies to simply ignore any LLM-based communication that you might submit. Worse, if these strategies were to become widespread, they might provide an excuse to reject any communications by feeding them into an unreliable "LLM detector" and issuing an automated "computer says no" even to hand-written correspondence.
It is also worth stressing that these cases are imagined, as compared to the very real coworker-abuse, spam, scam, fraud, and disinformation campaigns being waged in real life today.
Therefore, while legitimate uses might exist, it's hard to imagine that there's anywhere they would be genuinely valuable and sustainable. In the best case "AI" will provide a temporary advantage for underdogs that will provoke an arms race which the resource-advantaged adversaries will win in the long run, in the worst case the arms race itself will cement permanent structural change that will make things worse.
"Search" By Stealing
Most of the adversarial utility of "AI" is on the "write" side, since write-amplification is more obviously aggressive than reading. But the "read" side of LLMs - summarization and question-answering - can be a form of attack as well.
To begin with, the act of reading itself is currently enormously destructive, but that's arguably not a fundamental aspect of this technology. They could set reasonable rate-limits and respect things like robots.txt, as search engines have for decades now. They could also refrain from committing criminal levels of copyright infringement. But, today, using "AI" tools does suborn this sort of out-of-control crawling.
More insidiously, consider the scenario described in this YouTube video. The LTT Bros decided to try Linux again, and in the course of so doing, they had problems. When trying to solve these problems, they were faced with a choice: they could consult Reddit, or they could ask an LLM. Asking an LLM would "gaslight the heck out of" them, but they still found it preferable, because they would at least get an answer without getting yelled at.
Initially this sounds great. But it also means that you want to extract knowledge from a community, while mechanically eliding any values or norms that the community may want to impart as part of offering that knowledge. As someone who spent many years in a community tech support role, this is worrying. Many requests for support are people asking how to do things that will momentarily solve a superficial problem but create a long-term reliability problem or even an immediate security risk, that the question-asker doesn't want to hear about. Consider the question "I'm tired of entering my password so much, how do I make it so my laptop unlocks automatically". An obsequious chatbot will helpfully tell you how to do this without pushback.
But, this is also a sort of ethically murky area. The Linux community is somewhat famously, for many years now, a toxic cesspool of general hostility, misogyny, etc. It is certainly a good thing that people can get access to this knowledge without subjecting themselves to abuse. But it also means that the people with the power and the privilege to change the community for the better can just quietly withdraw, rather than fixing the problems. It also means that the positive elements of culture cannot be transmitted, and people will have no opportunity to learn about unknown unknowns.
In this case, the "adversarial" communication is with society. The thing that using an LLM for search lets you do is withdraw from society and avoid forming any personal connections. There are some personal connections which are painful and annoying, and so that can feel like a momentary balm. But the need to make connections in general is, like, the concept of society itself.
Who Am I Hurting?
LLMs are good at adversarial communication. They are so good at it, relative to their other benefits, that they will tend to make communications adversarial if you are not remaining vigilant about the possibility that it might do so. My request to you, dear reader, if you are going to use such tools, is to always ask yourself, "who might I be hurting, if I use an LLM for this?"
If you're using an "AI", who is its adversary? If you haven't given it one yet, who might the "AI" turn into an adversary? Who might you overwhelm with an asymmetric amount of output, or, if you're receiving information and not sending it, who are you taking that information from without consulting?
Figure out the answers to these questions and conduct yourself accordingly; the answer might be "yourself".
Acknowledgments
Thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor!
-
One of the reasons that software developers tend to prefer greenfield development is that when you are given a blank page, you can project your own specific understanding onto it. You can structure the codebase in a way that works for your brain, down to the variable naming conventions and the module layouts. LLM-assisted development makes everything into instant brownfield work, which makes developers instantly miserable; even those who are excited about the technology will frequently complain about how it feels like their agency has been stolen and their joy in the work has been diminished. But I digress. ↩
-
Modulo the massive amount of other externalities involved in using LLMs, of course, but I don't have the time or energy to get into those here. ↩
23 Jun 2026 8:06pm GMT
09 Jun 2026
Planet Twisted
Hynek Schlawack: How to Ditch Codecov for Python Projects
Codecov's unreliability breaking CI on my open source projects has been a constant source of frustration for me for years. I have found a way to enforce coverage over a whole GitHub Actions build matrix that doesn't rely on third-party services.
09 Jun 2026 12:00am GMT
22 May 2026
Planet Twisted
Glyph Lefkowitz: Opaque Types in Python
Let's say you're writing a Python library.
In this library, you have some collection of state that represents "options" or "configuration" for a bunch of operations. Such a set of options is a bundle of potentially ever-increasing complexity. Thus, you will want it to have an extremely minimal compatibility surface, with a very carefully chosen public interface, that is either small, or perhaps nothing at all. Such an object conveys state and might have some private behavior, but all you want consumers to be able to do is build it in very constrained, specific ways, and then pass it along as a parameter to your own APIs.
By way of example, imagine that you're wrapping a library that handles shipping physical packages.
There are a zillion ways to do it ship a package. There are different carriers who can ship it for you. There's air freight, and ground freight, and sea freight. There's overnight shipping. There's the option to require a signature. There's package tracking and certified mail. Suffice it to say, lots of stuff.
If you are starting out to implement such a library, you might need an object called something like ShippingOptions that encapsulates some of this. At the core of your library you might have a function like this:
1 2 3 4 5 |
|
If you are starting out implementing such a library, you know that you're going to get the initial implementation of ShippingOptions wrong; or, at the very least, if not "wrong", then "incomplete". You should not want to commit to an expansive public API with a ton of different attributes until you really understand the problem domain pretty well.
Yet, ShippingOptions is absolutely vital to the rest of your library. You'll need to construct it and pass it to various methods like estimateShippingCost and shipPackage. So you're not going to want a ton of complexity and churn as you evolve it to be more complex.
Worse yet, this object has to hold a ton of state. It's got attributes, maybe even quite complex internal attributes that relate to different shipping services.
Right now, today, you need to add something so you can have "no rush", "standard" and "expedited" options. You can't just put off implementing that indefinitely until you can come up with the perfect shape. What to do?
The tool you want here is the opaque data type design pattern. C is lousy with such things (FILE, pthread_*_t, fd_set, etc). A typedef in a header file can easily achieve this.
But in Python, if you expose a dataclass - or any class, really - even if you keep all your fields private, the constructor is still, inherently, public. You can make it raise an exception or something, but your type checker still won't help your users; it'll still look like it's a normal class.
Luckily, Python typing provides a tool for this: typing.NewType.
Let's review our requirements:
- We need a type that our client code can use in its type annotations; it needs to be public.
- They need to be able to consruct it somehow, even if they shouldn't be able to see its attributes or its internal constructor arguments.
- To express high-level things (like "ship fast") that should stay supported as we add more nuanced and complex configurations in the future (like "ship with the fastest possible option provided by the lowest-cost carrier that supports signature verification").
In order to solve these problems respectively, we will use:
- a public
NewType, which gives us our public name... - which wraps a private class with entirely private attributes, to give us an actual data structure, while not exposing the constructor,
- a set of public constructor functions, which returns our
NewType.
When we put that all together, it looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
As a snapshot in time, this is not all that interesting; we could have just exposed _RealShipOpts as a public class and saved ourselves some time. The fact that this exposes a constructor that takes a string is not a big deal for the present moment. For an initial quick and dirty implementation, we can just do checks like if options._speed == "fast" in our shipping and estimation code.
However, the main thing we are doing here is preserving our flexibility to evolve the related APIs into the future, so let's see how we might do that. For example, let's allow the shipping options to contain a concrete and specific carrier and freight method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
As a NewType, our public ShippingOptions type doesn't have a constructor. Since _RealShipOpts is private, and all its attributes are private, we can completely remove the old versions.
Anything within our shipping library can still access the private variables on ShippingOptions; as a NewType, it's the same type as its base at runtime, so it presents minimal1 overhead.
Clients outside our shipping library can still call all of our public constructors: shipFast, shipNormal, and shipSlow all still work with the same (as far as calling code knows) signature and behavior.
If you need to build and convey some state within your public API, while avoiding breakages associated with compatibility churn, hopefully this technique can help you do that!
Acknowledgments
Thanks for reading, and thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor.
-
The overhead is minimal, but it is not completely zero. The suggested idiom for converting to a
NewTypeis to call it like a function, as I've done in these examples, but if you are wanting to use this pattern inside of a hot loop, you can use# type: ignore[return-value]comments to avoid that small cost. ↩
22 May 2026 12:33am GMT