❌

Normal view

MacVoices #26248: AI Development: Start Small, Learn Safely, Then Build Bigger with Adam Engst (3)

By: Chuck
3 September 2026 at 20:46
Adam Engst explains why AI software development should be approached as an evolution, starting with safe, verifiable data analysis, advancing to simple scripts, and eventually building applications. He distinguishes programming from development, arguing that non-programmers can act as product managers who direct AI, demand security checks, define requirements, and create software tailored to problems existing apps do not solve. (3) 

💾

💾

Mojo 1.0

19 August 2026 at 21:47

Modular (tweet, beta, Hacker News):

Today, the Mojo language officially reaches 1.0: a milestone the language has been building toward since its first release in 2023. Mojo has grown into a general-purpose language with a vibrant developer community writing their own libraries, tools, and applications on top of it. With Mojo 1.0, developers can now build for the long-term on a stable, production-ready language foundation.

[…]

Importantly, Mojo 1.0 does not mark the end of the language’s evolution, but it is an important milestone on a longer journey. During the 1.x timeframe, changes should primarily be additive, giving developers confidence that the language will not continually shift beneath them. Breaking changes may still be made, but will be managed with care, following the standards of how mature languages (e.g. C++) evolve over time.

Modular (Hacker News):

We are happy to announce that the Mojo🔥 language is now fully open source under the Apache 2.0 license (with LLVM exceptions)! The source code for the Mojo compiler, tooling, and everything else you need to build the language are now available in our modular GitHub repository.

[…]

The Mojo standard library has been accepting contributions since 2024, and we’re grateful for everyone that has helped advance the language. One learning (particularly in today’s era of AI coding) is that we need to be deliberate about how we handle contributions. As such, we aren’t ready to take contributions to the compiler and tooling. We aim to accept contributions to the compiler and tooling by the end of this year, and we’ll share more details when we can.

Abdul Dakkak, Chris Lattner, and Jeff Niu (2023):

This talk will give an overview of Mojo 🔥, a new programming language in the Python family built on MLIR and LLVM. Mojo aims to bridge the programmability and performance gap in systems programming by combining Python’s ergonomics and ecosystem with C++ and CUDA-level performance into a single language. We will describe how Mojo leverages the MLIR and LLVM infrastructures to provide meta-programming, user-defined code transformations, hardware backends, adaptive compilation, and auto-tuning to enable developers to achieve performance without sacrificing programmability.

Gergely Orosz (2025, tweet):

Chris Lattner is one of the most influential engineers of the past two decades. He created the LLVM compiler infrastructure and the Swift programming language – and Swift opened iOS development to a broader group of engineers. With Mojo, he’s now aiming to do the same for AI, by lowering the barrier to programming AI applications.

I sat down with Chris in San Francisco, to talk language design, lessons on designing Swift and Mojo, and – of course! – compilers.

See also: LPython (Hacker News).

Previously:

The best new features in Python 3.15

10 August 2026 at 11:00

Python 3.15 is one of the most feature-packed Python releases in many a moon, and the first release candidate has just arrived. Here’s a rundown of the biggest, boldest, and most important innovations, changes, and fixes.

Lazy imports

A long-asked for feature, lazy imports allow imports to be processed only when they’re actually used by the program. For slow-importing modules that impose a large cost on a program’s startup time, you can defer that cost to when the code of that module will actually be executed.

You can use lazy imports explicitly using the new lazy import syntax, but you can also force code with conventional imports to behave lazily, either programmatically or by using an environment variable. This makes it easy to make existing code take advantage of this feature without tons of rewriting. Best of all, there’s no drawback to making imports lazy: they otherwise behave exactly as intended.

The frozendict built-in type

Only rarely does Python add a new data type, but this is a long-debated and long-desired addition: the frozen dictionary. The frozendict behaves like a regular dictionary, except that it’s immutable (you can’t add, remove, or change elements) and it’s hashable (so you can use it as a key in another dictionary, for instance).

The sentinel() built-in type

Another new addition to the language is intended to replace a common and problematic Python pattern: creating a unique sentinel object (as an alternative to None where None could be a valid value, for example) by using object(). The new syntax ,sentinel("NAME"), creates unique objects that compare only to themselves via the is operator. These objects can be type-checked properly, and they have an informative representation instead of just a random object descriptor.

Tachyon, a statistical sampling profiler

The long-standing cProfile module profiles Python code deterministically—that is, it tracks and records every single call. That makes it precise, but it also means a cProfile-tracked program runs far slower than normal. A new profiling module in Python 3.15, profiling.sampling, uses statistical sampling methods to garner useful information about performance at a fraction of the impact on the program’s speed. The existing cProfile profiler is still available—it’s not going away—but has a new alternate name, profiling.tracing.

An upgraded JIT

CPython’s built-in just-in-time (JIT) compiler debuted in Python 3.13. Its long-term goals are to make Python programs run faster without any changes to code, in something of the same way the alternate Python runtime PyPy can speed things up. And it comes without the cost of changing to a totally different interpreter with some of its own limitations.

The first couple of revisions of the JIT didn’t promise, or deliver, a great deal of additional speed, as they were more about laying a foundation for future improvements. With Python 3.15, though, the JIT is now showing an 8% to 13% geometric mean performance improvement over standard CPython, depending on the platform and workload. The biggest changes include a new tracing front end (to enable more speedups on more kinds of code), the use of register allocation for faster and more memory-efficient work, better machine code generated by the JIT, and additional optimizations such as eliminating reference counts for some classes of objects.

It’s worth experimenting with enabling the JIT for workloads to see if there’s any measurable difference. But note that further development on the JIT is now subject to some firmly laid-out guidelines regarding the performance improvement it needs to provide before it can be considered a fully supported part of Python, and not just an experimental sideline.

Better error messages

Error messages in Python have been made more precise, detailed, and useful over the last couple of versions, and Python 3.15 continues that work. The highlights:

  • Suggestions for missing names (“x has no attribute ‘y‘. Did you mean ‘xyz‘?”) now include suggestions from the members of a given object, and not just the object itself.
  • Suggestions now also cover checks for deleting attributes, not just accessing them.
  • If the interpreter can’t come up with a suggestion for a method based on fuzzy name matching via Levenshtein distance, it consults a list of names commonly used in other languages for such methods. For example, if you attempt to use list.push() (a JavaScript method), the interpreter suggests .append(), the proper method for Python lists.

Type system improvements

The TypedDict class, which lets you create dictionaries with predefined keys and type-hinted keys and values, adds support for two new arguments in its definition. The closed argument lets you specify if only the keys specified can be used at runtime. The extra_items argument lets you specify additional keys at runtime, but only keys with a value of a specified type.

The TypeForm type definition lets you represent the value that results from evaluating a type expression. With this, type annotations can be used in places where the type itself is being used as a value—for instance, variations on operations like typing.cast or even isinstance, or as part of how a third-party type-checking tool works.

Unpacking in comprehensions

This is another long-requested feature. If you wanted to completely unpack or “flatten” a nested object using a comprehension, you used to need a function like itertools.chain() or you would have to write a nested comprehension with an ugly syntax:

x = [[1,2,3],[4,5],[6]]
y = [a for b in x for a in b]
>>> [1, 2, 3, 4, 5, 6] # y

Unpacking in comprehensions using the star operator lets you save yourself a step:

x = [[1,2,3],[4,5],[6]]
y = [*a for a in x]
>>> [1, 2, 3, 4, 5, 6] # y

Unpacking with ** also works, for instance as a way to flatten and combine dictionaries:

dicts = [{'a': 1}, {'b': 2}, {'a': 3}]
y = {**d for d in dicts}
>>> {'a': 3, 'b': 2}

Finally, this kind of unpacking can also be used to form generator expressions:

(*x for x in ["ab","cd","ef"])

The expression above creates a generator that yields:

['a', 'b', 'c', 'd', 'e', 'f']

Reverting the incremental garbage collector

Finally, Python 3.15 takes an important U-turn. Python 3.14 featured a major change to its garbage collection system—an incremental garbage collector intended to reduce the amount of program-stopping time needed to collect garbage. Unfortunately, many users reported the new garbage collector increases process memory usage, sometimes dramatically. Python 3.15 will revert back to the older generational garbage collector used in Python 3.13 and before. The incremental collector may return in a future version, but not without additional work done on it to keep this problem from resurfacing.

Other changes

  • A new math module, math.integer, gives you functions for integer-specific math such as greatest common divisor or integer square root.
  • The Stable ABI was created to make it easier to write CPython extensions that didn’t have to be recompiled to be compatible with multiple point revisions of Python. With Python 3.15, developers writing extensions that use the free-threaded (aka “no-GIL”) build of Python can now use the Stable ABI. This will require some rewriting of extensions that use the Stable ABI; it isn’t something you can accomplish simply by recompiling code with different headers.
  • CPython builds now have frame pointers enabled by default where supported. This makes it easier and faster for the stack to unwind, so that CPython can be analyzed more reliably by system-level profiling and debugging tools.
  • UTF-8 is now the default encoding for Python globally. You no longer need to specify UTF-8 as an encoding when, for instance, reading text from files. You can disable this behavior using a command-line flag or environment variable.

Soothe your Python frustrations the smart way

29 July 2026 at 10:00

For a programming language that is prized for being convenient and easy, Python has some surprising barbs and sharp edges. Find out how a new pip flag can make it easier to work with project dependencies apart from the project itself. Get the skinny on why standalone Python programs are hard to assemble, and what you can do about it. See how Python’s new Installation Manager for Microsoft Windows lets multiple versions coexist on the same system. And find out what will be shipped when Python 3.15 debuts later this year.

Top picks for Python readers on InfoWorld

New pip flag fixes longstanding Python frustration
A longstanding gripe with Python’s pip package manager has a happy ending. The next version of pip will let you install a package’s requirements without installing the package itself.

Why it’s so hard to create stand-alone Python apps
Python’s best features, its dynamism and flexibility, have a downside: They make it tough to package Python apps as standalone programs. Not impossible, though; just … complicated.

The best new features in Python 3.15
If you haven’t grabbed the beta release of Python 3.15 yet, it’s ready when you are. Here’s a rundown of all the best bits you can start using right away.

Get started with the new Python Installation Manager
This all-in-one Python version management tool is now the recommended, default way to set up and manage Python versions on Microsoft Windows — including betas, release versions, and the special JIT and free-threaded builds, too.

More good reads and Python updates elsewhere

Ruff linter gets a massive upgrade in version 0.16
The speedy, Rust-powered linter and formatter for Python now supports over 400 rules by default, making for a more useful, zero-configuration, out-of-the-box linting experience.

What every Python developer should know about the CPython ABI
A massive, multilayered unpacking of that most intimidating of Python internals, the CPython application binary interface. Even if you never plan to write a CPython extension or contribute to the CPython project, the details laid out here are eye-opening.

Free-threaded Python: past, present, and future
At PyCon 2026, CPython core developer Thomas Wouters gave a talk about how Python came to support threads via the GIL, and how its developers went about removing the GIL over many years and multiple attempts. Excellent recap available at LWN.net.

PEP 837: Extensible JSON serialization
A newly introduced Python Enhancement Proposal (intended for Python 3.16) to make Python’s native JSON serialization more extensible, for instance to allow new types to automatically support serialization via a __json__ method.

The Microsoft agent framework wars are over. The real architecture decision starts now

24 July 2026 at 10:00

Over the past year, I had the same conversation with almost every team starting an AI initiative. Should we build on Semantic Kernel, AutoGen or Foundry?

At first it felt like the most important architectural decision we’d make. Each framework had its own philosophy, each promised to be the foundation for enterprise AI, and picking the wrong one felt like an expensive mistake. I spent a lot of time helping teams weigh the trade-offs.

Looking back, I think we were asking the wrong question. I certainly was.

I watched teams spend months debating SDKs while the decisions that actually decided whether their applications survived production went unexamined. Some built elaborate orchestration layers for workflows that a few deterministic functions would have handled. Others avoided agent frameworks entirely and later found they’d designed themselves into a corner.

Then Microsoft settled it for us. It introduced the unified Agent Framework, quietly moved Semantic Kernel and AutoGen into maintenance mode, and the debate I’d spent months refereeing was suddenly over. Turns out the answer to “which of the three” was “none of the three, here’s a fourth.” The framework hit version 1.0 and general availability in April 2026, stable across .NET and Python.

What surprised me wasn’t the decision. It was how fast a debate that had eaten so much of our attention stopped mattering. Microsoft changed the menu.

It didn’t change the meal.

The framework was never the hard part

Framework selection dominated almost every early conversation I had about enterprise agents. Which SDK do we standardize on? Which orchestration model gives us the most flexibility? Which one is Microsoft actually betting on?

Fair questions. But after a year of watching these projects play out, I’ve slowly come around to a different view. Those weren’t the questions that decided anything.

The first question I ask now is much smaller. Does this thing actually need an agent?

It sounds obvious, and I still get it wrong sometimes. But it’s the mistake I see most. On one project, a team spent weeks designing a multi-agent workflow for a process that ran the same four steps every time: read a document, validate it, call an API, send a notification. The diagrams looked great. The system in production didn’t. A few well-tested functions would have been easier to build, easier to maintain and a lot easier to trust.

Part of this is just that “agent” has become the word everyone reaches for. Sometimes it’s the right call. Sometimes it’s a workflow we already knew how to build, wearing a newer label. An agent earns its complexity when it genuinely has to decide things you can’t predetermine, choosing between tools, adapting to what it finds, working out its own next step. If you already know every step, you have a workflow, and a workflow is usually the better engineering choice. The consolidation didn’t change that. It just made it easier to see.

What building production agents actually taught me

Once I stopped fixating on frameworks, the same three problems kept showing up. None of them had anything to do with the SDK.

Context beats model choice

Early on I spent a lot of time comparing models, the way you’d agonize over a restaurant menu and then order what you always order. Now I spend most of it thinking about context, which is far less fun and far more useful.

I’ve watched good models fail because they were handed too much, not too little. One team I worked with gave the model access to nearly every internal document they had on the theory that more information meant better answers. It went the other way. Responses got slower, less consistent and sometimes skipped right past the thing that actually mattered. When we cut the context down to only what the task needed, the quality jumped almost immediately. I didn’t predict that. It taught me to be suspicious of “just give it everything.”

The best agent systems I’ve worked on weren’t the ones with the biggest context windows. They were the ones careful about what reached the model, and when. That’s not something the framework hands you.

Failure is where the real work is

Most agent demos look great because they’re built around the happy path. Production doesn’t extend that courtesy.

I remember a project where everything held up in testing. Then a downstream API timed out after the agent had already completed several earlier steps. We couldn’t just restart, because part of the business process had already gone through. We ended up spending far more time on recovery logic than we ever spent on prompts. That project changed how I think about this work. The hard part was never getting the model to make a decision. It was making sure the system didn’t fall apart when reality refused to follow the script.

Tool calls fail partway through. APIs return inconsistent data. Models call the same tool over and over because the last answer wasn’t what they wanted. That’s not the exception; that’s a normal Tuesday. Whether you retry, roll back, pause for a human or push on with partial results is a judgment call, and no framework is going to make it for you.

Identity is the real security boundary

This one surprised me most. The moment an agent stops being a chatbot and starts touching real business systems, identity matters more than orchestration.

Every project gets to the same question eventually. Who is this agent actually acting as? The developer’s credentials? A service account? The user who asked? Get it wrong and you’ve built something autonomous running with more access than any single person should have, which is exactly the kind of thing that looks fine until an audit. The Agent Framework, like most modern tooling, makes it easier to wire agents to tools through standards like the Model Context Protocol. That helps. But where human approval belongs, what needs extra authorization, how much rope to give the thing, those are still yours to decide.

The surprises weren’t technical

Here’s what I didn’t see coming. The hardest part of last year wasn’t technical at all. It was organizational. The moment a team heard “agent,” expectations shifted under everyone’s feet. Business stakeholders started expecting full autonomy. Developers assumed the thing could reason its way through anything. People started designing for flexibility before we’d even agreed on what problem we were solving. The word did damage before any code did. I found myself spending as much time resetting expectations as I did discussing architecture.

Build for change, not for today’s winner

I don’t think the teams that struggled last year picked the wrong framework. Semantic Kernel was reasonable. AutoGen was reasonable. Foundry made sense for plenty of cases. I’d have signed off on any of them.

The ones that got hurt put all their eggs in one framework, treating it as the foundation of the whole system instead of as one more dependency. Microsoft provided a migration path. But teams that had tightly coupled their applications to framework-specific abstractions discovered that migrating and rewriting are not the same thing. That wasn’t Microsoft’s doing. It was their own architecture’s. The teams that moved easily had kept their business logic, prompts and orchestration loose enough to evolve independently of any one SDK. For them, the change was a manageable project, not a teardown.

For what it’s worth, nobody I work with is treating this as an emergency. Most are moving the smaller workloads first, watching how they behave and leaving the production-critical systems alone until they actually understand the new abstractions. That’s the right instinct. And I doubt this is the last consolidation we’ll see, the ecosystem is still young, frameworks will keep absorbing each other and over time the differences between them will be operational more than architectural.

I don’t regret the framework debates, honestly. They were reasonable at the time. What changed wasn’t Microsoft’s roadmap.

It was mine. Watching these systems run in production taught me that the framework is the easiest piece to swap out. Recovery logic, context management, security boundaries, the business workflow itself, those stay with you long after today’s SDK gets replaced by tomorrow’s.

So, Microsoft made one decision easier by turning three frameworks into one. Good. Five years from now we’ll be on different tools, and we’ll still be asking the same handful of questions.

Does this actually need an agent? Does it have the right context? Can it recover when something breaks, because something will? Is it acting as the right person?

Those questions outlast every rewrite. That’s where I’ve learned to put my effort.

Frameworks come and go. Good architecture has to survive all of them.

This article is published as part of the Foundry Expert Contributor Network.
Want to join?

New pip flag fixes longstanding Python frustration

23 July 2026 at 19:38

A new version of Python’s native package management tool, pip, will remove a limitation that has frustrated Python developers for years. If you wanted to install the dependencies for a given package, but not install the package itself, you were stuck. Either you had to extract the dependency list from the package and install it by hand, or you had to build the whole package anyway.

Why was this a problem? Sometimes, you want only the dependencies for a package—for instance, as a way to create a separate environment for testing or another project. If you’re making source distributions via CI, some requirements might be needed to make the source distribution but aren’t actually included in it (e.g., Cython). You would need to install these requirements somewhere—apart from the project itself—to perform the build step.

A dependencies-only install mode for packages is a long-requested feature. Developer James O’Claire found many examples of such requests, along with various workarounds. Most of those involved third-party solutions of some sort.

Now, a new feature set to land in pip version 26.2 will fix this problem from the inside. The command pip install --only-deps will install only the dependencies for a given package. Note that it will not install build dependencies for the package, but only runtime dependencies.

Note that you can accomplish this with existing third-party tools. For instance, uv sync --no-install-project has the same behavior. But having this functionality right inside pip means you don’t have to turn to external tooling—or ugly hacks—to solve the problem. This may be an example of how third-party projects like uv (a superfast Python package installer) are inspiring native Python features where they make sense.

pip 26.2 is scheduled to be released by the end of July 2026.

Dissecting Apple’s Sparse Image Format (ASIF)

30 June 2026 at 20:48

Erik Schamper:

ASIF takes a lot of inspiration from existing virtual disk formats. Practically, that means it’s another sparse virtual disk format, and functions very similar to sparse VMDK, VHDX or QCOW2 files (for the uninitiated, it allow you to store a large disk, or file, in a smaller, “sparse” manner).

Shortly before the release of macOS Tahoe (late 2025), I thought it’d be a fun exercise to try and write a parser for ASIF files.

Previously:

Write cleaner and faster Python code

19 June 2026 at 10:00

Meta’s long-awaited Pyrefly linter is out in a 1.0 version, and the forthcoming Python 3.15 has a super-efficient sampling profiler. Plus we have a comprehensive rundown of Python’s indispensable virtual environments — and a warning about a novel breed of malware that exploits Python’s package ecosystem.

Top picks for Python readers on InfoWorld

How to use virtual environments in Python
Isolate and protect your Python projects from each other, and empower them to do more, with virtual environments and their native-to-Python tooling.

Pyrefly 1.0: A fast, forward-looking Python linter
The first full release of Meta’s long-awaited linting and type checking tool for Python delivers speed and offers advanced features for type-checking PyTorch and Django projects.

Hands-on with the new sampling profiler in Python 3.15
Among Python 3.15’s best new features is a sampling profiler, for instrumenting your code and finding its bottlenecks with a minimum of performance impact or fuss. See up-close how it works.

All about Hades, the supply-chain malware that hides in Python packages
It hides in Python packages. It replicates itself across systems. It fools LLM-based code analysis tools into ignoring it. And there may be a lot more like it to come.

More good reads and Python updates elsewhere

Python Steering Council calls for temporary pause on JIT project
The requested pause stays in place until a proper Standards Track PEP lands for the experimental JIT (just-in-time) compiler, the better to describe how the JIT will be a formal and supported part of Python.

Pyodide 314.0: Pyodide packages on PyPI
Thanks to PEP 783, Python packages built with Pyodide (Python ported to WebAssembly) can be installed straight from PyPI instead of through Pyodide — another step closer to Py-on-Wasm becoming an everyday thing.

All about that Python 3.14 garbage collection rigmarole
A new garbage collector introduced in Python 3.14 was yanked at the last minute due to reports of higher memory usage. Here’s a deep dive into what changed for the worse and why.

Are you really expected to run five type checkers now?
No, but you should keep your options open. This blog post from a Pyrefly contributor recommends choosing one of the major offerings (Mypy, Pyrefly, Pyright, ty, Zuban, etc.), but also getting to know the others too.

How to use virtual environments in Python

10 June 2026 at 10:00

Of all the reasons Python is a hit with developers, one of the biggest is its broad and ever-expanding selection of third-party packages. Convenient toolkits for everything from ingesting and formatting data to high-speed math and machine learning are just an import or pip install away.

But what happens when those packages don’t play nice with each other? What do you do when different Python projects need competing or incompatible versions of the same add-ons? That’s where Python virtual environments come into play.

What are Python virtual environments?

A virtual environment is a way to have multiple, parallel instances of the Python interpreter, each with different sets of packages and different configurations. Each virtual environment contains a discrete copy of the Python interpreter, including copies of its support utilities (such as the package manager pip).

The packages installed in each virtual environment are seen only in that virtual environment and no other. Even large, complex packages with platform-dependent binaries can be corralled off from each other in virtual environments.

Why use Python virtual environments?

There are a few common use cases for a virtual environment:

  1. You’re developing multiple projects that depend on different versions of the same packages, or you have a project that must be isolated from certain packages because of a namespace collision. This is the most standard use case.
  2. You’re working in a Python environment where you can’t modify the site-packages directory. This may be because you’re working in a highly controlled environment, such as managed hosting, or on a server where the choice of interpreter (or packages used in it) can’t be changed because of production requirements.
  3. You want to experiment with a specific combination of packages under highly controlled circumstances, for instance to test cross-compatibility or backward compatibility.
  4. You want to run a “baseline” version of the Python interpreter on a system with no third-party packages, and only install third-party packages for each individual project as needed.

Nothing says you can’t simply unpack a Python library into a subfolder of a project and use it that way. Likewise, you could download a standalone copy of the Python interpreter, unpack it into a folder, and use it to run scripts and packages devoted to it.

But managing such cobbled-together projects soon becomes difficult. It only seems easier to do that at first. Working with packages that have binary components, or that rely on elaborate third-party dependencies, can be a nightmare. Worse, reproducing such a setup on someone else’s machine, or on a new machine you manage, is tricky.

The best long-term solution is to use Python’s native mechanisms for creating, reproducing, and working with virtual environments.

How to use virtual environments in Python 3

Python has native tooling for virtual environments that makes the whole process quite simple. This wasn’t always the case, but now all supported versions of Python use the native virtual environment tool, venv.

Create the Python virtual environment

To create a virtual environment in a given directory, type:

python3 -m venv /path/to/venv

For instance, to create the virtual environment in the current directory, using the subdirectory .venv type:

python3 -m venv .venv

On Microsoft Windows, you can use py instead of python3 to reliably access an installed Python version. (See this article for more about using the py launcher in Windows.)

The exact name for the virtual environment directory is arbitrary, but it’s typically .venv.

The whole process of setting up the virtual environment may take a minute or two. When it’s finished, you should see that directory with a few subdirectories in it. The most important subdirectory is bin on Unix or Scripts on Windows, which is where you’ll find the copy of the Python interpreter for the virtual environment along with its utilities.

Note that because each virtual environment contains its own copy of the Python interpreter, it can be fairly large. A Python 3.13 virtual environment will consume anywhere from 14MB to 26MB of disk space, depending on the operating system.

Venvs and version control

If you are setting up a virtual environment in a project directory managed with some kind of version control system (e.g., Git), exclude the environment directory from tracking before you make any commits after creating the venv.

Venvs should not be tracked along with their associated code, as they are meant to be destroyed and recreated as needed. You should, however, track the requirements.txt or pyproject.toml files associated with the project, as those are used to describe what gets installed in the venv for that project.

Activate the Python virtual environment

Before you can use this virtual environment, you need to explicitly activate it. Activation makes the virtual environment the default Python interpreter for the duration of a shell session.

You’ll need to use different syntax for activating the virtual environment depending on which operating system and command shell you’re using.

  • On Unix or MacOS, using the bash shell: source /path/to/venv/bin/activate
  • On Unix or MacOS, using the csh shell: source /path/to/venv/bin/activate.csh
  • On Unix or MacOS, using the fish shell: source /path/to/venv/bin/activate.fish
  • On Windows using the Command Prompt: path/to/venv/Scripts/Activate.bat
  • On Windows using PowerShell: path/to/venv/Scripts/Activate.ps1

Note that the activated environment only works for the context it was activated in. For instance, if you launch two instances of PowerShell, A and B, and you activate the virtual environment in instance A, that environment will apply only to A, not B.

Many Python IDEs will automatically detect and activate a virtual environment if one is found in the current project directory. Visual Studio Code, for instance, can do this when the Python extension is enabled. Opening a terminal inside Visual Studio Code will automatically activate the selected virtual environment. PyCharm automatically creates a virtual environment for each new project and enables it automatically.

Configure and use the Python virtual environment

Once you’ve activated the new virtual environment, you can use the pip package manager to add and change packages for it. You’ll find pip in the Scripts subdirectory of the virtual environment on Windows, and in the bin subdirectory on Unix OSes.

If you’re already familiar with the way pip works, you’re set. It should be just the same in a virtual environment. Just make sure you’re using the instance of pip that manages packages for the virtual environment in the context where it was activated—e.g., the bash session or Windows CLI/PowerShell session. If you want to verify that you’re using the right pip and the right virtual environment, type pip -V and check that the path it displays points to a subdirectory of your virtual environment.

Note that when you want to upgrade pip in a virtual environment, it’s best to use the command python3 -m pip install -U pip. This ensures the upgrade process is run in such a way that Python doesn’t lock crucial files. The command pip install -U pip may not be able to complete the upgrade properly.

To use the virtual environment you created to run Python scripts, simply invoke Python from the command line in the context where you activated it. For instance, to run a script, just run python3 myscript.py.

With PyCharm, you can use the IDE’s own package management interface to manage the packages installed in your project.

Managing packages in Python virtual environments

When you create a new virtual environment, pip will be installed, but that’s all. You’ll need to install any other packages you want to use in the environment. For projects with complex requirements, it is customary to keep in the root of the project a requirements.txt file that lists the requirements for the project. This way, if you need to recreate the virtual environment, you can reinstall all of the needed packages with the command pip install -r requirements.txt.

More recently, a new project metadata format has emerged for Python projects, called pyproject.toml. A pyproject.toml file contains the package requirements of the project, but also a great deal of other information about it. To install those requirements, you’d run pip install . in the same directory as the pyproject.toml file.

Note that the copy of pip that lives in a virtual environment is local to that virtual environment. Each virtual environment has its own copy, which will need to be updated and maintained independently. This is why you may get warnings about pip being out of date in some virtual environments but not others; pip has to be updated in each virtual environment separately.

Deactivating the Python virtual environment

When you’re done using the virtual environment, you can just terminate the session where you were using it. If you want to continue to work in the same session but with the default Python interpreter instead, type deactivate at the prompt. Windows users on the Command Prompt need to run deactivate.bat from the Scripts subdirectory, but Unix users and Windows users running PowerShell can simply type deactivate in any directory.

Removing the Python virtual environment

Virtual environments are self-contained. When you no longer need the virtual environment, you can simply delete its directory. Just make sure you first close any running copies of Python that use the virtual environment.

Whenever you want to refresh or recreate a virtual environment, you can simply delete the current environment directory and recreate the venv as described above (see “Managing packages in Python virtual environments”). This is typically the easiest way to upgrade a project to a newer version of Python: delete or temporarily re-name the venv directory (in case the new version doesn’t work), then create a new one and install the project requirements into it.

If you have many older projects that aren’t being actively used, you can remove their environments to save space. They can be recreated easily whenever needed, although you will want to note which version of Python was used to create them along with the needed packages (typically recorded in requirements.txt or pyproject.toml).

Relocating the Python virtual environment

It’s tempting to assume a virtual environment can be copied and moved around along with its project. Don’t do this. Virtual environments are tied to the location of the Python installation on the system where they’re created. If you want to move the project to another system, leave out the venv directory, and recreate the venv on the target machine. Do copy and move the requirements.txt or pyproject.toml file with the project, because those files are needed to recreate the venv on the other system.

How to use virtual environments in Python 2

With Python 2, virtual environments aren’t a native feature of the language. Instead, you need to install third-party libraries to create and manage virtual environments.

The most popular and widely used of these projects is virtualenv, which handles creating the directory structure and copying the needed files into a virtual environment. To install virtualenv, just use pip install virtualenv. To create a virtual environment directory with it, type virtualenv /path/to/directory. Activating and deactivating the virtual environment works the same way as it does for virtual environments in Python 3 (see above).

Note that Python 2 should not be used for any new development. Virtual environments in Python 2, like Python 2 itself, should be used only for the maintenance of legacy projects that should eventually be migrated to Python 3.

Virtual environments and the main Python package directory

Normally, when you create a venv, it cannot work with the packages already present in the Python installation that created it. This is by design, as it ensures that packages installed globally don’t interfere with local ones.

You can override this behavior, but only when you first create a virtual environment. If you pass the flag --system-site-packages to venv when you run it, the created venv will have access to the parent Python’s package directory.

Using Python virtual environments with Jupyter notebooks

If you’re using Jupyter notebooks (aka IPython notebooks), and you already have Jupyter installed systemwide, create your virtual environment and activate it. Then, from your virtual environment directory, run pip install ipykernel to add the needed components for IPython. Finally, run ipython kernel install —user —name=, where project_name is a name you want to associate with that particular project. From there you should be able to launch Jupyter and switch to the IPython kernel you installed inside the virtual environment.

Upgrading Python virtual environments

When you upgrade a Python runtime on your system, virtual environments that use that version of Python aren’t automatically upgraded. That’s your responsibility. And that’s by design, because unwitting upgrades to Python versions can break their attendant packages.

If you’ve upgraded an existing Python interpreter with a minor point upgrade—e.g., from Python 3.13.1 to Python 3.13.3—you can upgrade any corresponding virtual environments easily enough. From a command prompt in the project directory, enter:

python -m venv /path/to/venv --upgrade

Don’t activate the virtual environment beforehand, or the upgrade may not work.

Alternatively, as noted above (see “Removing the Python virtual environment”), you could elect to remove the venv completely and recreate it using your requirements.txt or pyproject.toml file.

If you’ve installed a major new version of Python—e.g., you already have Python 3.10 and you now install Python 3.11 alongside it—you’ll need to create a new virtual environment that specifically uses the new major point version. Do not attempt to upgrade an existing virtual environment to a higher major point version of Python.

How to use virtual environments in Python

9 June 2026 at 17:05

Of all the reasons Python is a hit with developers, one of the biggest is its broad and ever-expanding selection of third-party packages. Convenient toolkits for everything from ingesting and formatting data to high-speed math and machine learning are just an import or pip install away.

But what happens when those packages don’t play nice with each other? What do you do when different Python projects need competing or incompatible versions of the same add-ons? That’s where Python virtual environments come into play.

What are Python virtual environments?

A virtual environment is a way to have multiple, parallel instances of the Python interpreter, each with different sets of packages and different configurations. Each virtual environment contains a discrete copy of the Python interpreter, including copies of its support utilities (such as the package manager pip).

The packages installed in each virtual environment are seen only in that virtual environment and no other. Even large, complex packages with platform-dependent binaries can be corralled off from each other in virtual environments.

Why use Python virtual environments?

There are a few common use cases for a virtual environment:

  1. You’re developing multiple projects that depend on different versions of the same packages, or you have a project that must be isolated from certain packages because of a namespace collision. This is the most standard use case.
  2. You’re working in a Python environment where you can’t modify the site-packages directory. This may be because you’re working in a highly controlled environment, such as managed hosting, or on a server where the choice of interpreter (or packages used in it) can’t be changed because of production requirements.
  3. You want to experiment with a specific combination of packages under highly controlled circumstances, for instance to test cross-compatibility or backward compatibility.
  4. You want to run a “baseline” version of the Python interpreter on a system with no third-party packages, and only install third-party packages for each individual project as needed.

Nothing says you can’t simply unpack a Python library into a subfolder of a project and use it that way. Likewise, you could download a standalone copy of the Python interpreter, unpack it into a folder, and use it to run scripts and packages devoted to it.

But managing such cobbled-together projects soon becomes difficult. It only seems easier to do that at first. Working with packages that have binary components, or that rely on elaborate third-party dependencies, can be a nightmare. Worse, reproducing such a setup on someone else’s machine, or on a new machine you manage, is tricky.

The best long-term solution is to use Python’s native mechanisms for creating, reproducing, and working with virtual environments.

How to use virtual environments in Python 3

Python has native tooling for virtual environments that makes the whole process quite simple. This wasn’t always the case, but now all supported versions of Python use the native virtual environment tool, venv.

Create the Python virtual environment

To create a virtual environment in a given directory, type:

python3 -m venv /path/to/venv

For instance, to create the virtual environment in the current directory, using the subdirectory .venv type:

python3 -m venv .venv

On Microsoft Windows, you can use py instead of python3 to reliably access an installed Python version. (See this article for more about using the py launcher in Windows.)

The exact name for the virtual environment directory is arbitrary, but it’s typically .venv.

The whole process of setting up the virtual environment may take a minute or two. When it’s finished, you should see that directory with a few subdirectories in it. The most important subdirectory is bin on Unix or Scripts on Windows, which is where you’ll find the copy of the Python interpreter for the virtual environment along with its utilities.

Note that because each virtual environment contains its own copy of the Python interpreter, it can be fairly large. A Python 3.13 virtual environment will consume anywhere from 14MB to 26MB of disk space, depending on the operating system.

Venvs and version control

If you are setting up a virtual environment in a project directory managed with some kind of version control system (e.g., Git), exclude the environment directory from tracking before you make any commits after creating the venv.

Venvs should not be tracked along with their associated code, as they are meant to be destroyed and recreated as needed. You should, however, track the requirements.txt or pyproject.toml files associated with the project, as those are used to describe what gets installed in the venv for that project.

Activate the Python virtual environment

Before you can use this virtual environment, you need to explicitly activate it. Activation makes the virtual environment the default Python interpreter for the duration of a shell session.

You’ll need to use different syntax for activating the virtual environment depending on which operating system and command shell you’re using.

  • On Unix or MacOS, using the bash shell: source /path/to/venv/bin/activate
  • On Unix or MacOS, using the csh shell: source /path/to/venv/bin/activate.csh
  • On Unix or MacOS, using the fish shell: source /path/to/venv/bin/activate.fish
  • On Windows using the Command Prompt: path/to/venv/Scripts/Activate.bat
  • On Windows using PowerShell: path/to/venv/Scripts/Activate.ps1

Note that the activated environment only works for the context it was activated in. For instance, if you launch two instances of PowerShell, A and B, and you activate the virtual environment in instance A, that environment will apply only to A, not B.

Many Python IDEs will automatically detect and activate a virtual environment if one is found in the current project directory. Visual Studio Code, for instance, can do this when the Python extension is enabled. Opening a terminal inside Visual Studio Code will automatically activate the selected virtual environment. PyCharm automatically creates a virtual environment for each new project and enables it automatically.

Configure and use the Python virtual environment

Once you’ve activated the new virtual environment, you can use the pip package manager to add and change packages for it. You’ll find pip in the Scripts subdirectory of the virtual environment on Windows, and in the bin subdirectory on Unix OSes.

If you’re already familiar with the way pip works, you’re set. It should be just the same in a virtual environment. Just make sure you’re using the instance of pip that manages packages for the virtual environment in the context where it was activated—e.g., the bash session or Windows CLI/PowerShell session. If you want to verify that you’re using the right pip and the right virtual environment, type pip -V and check that the path it displays points to a subdirectory of your virtual environment.

Note that when you want to upgrade pip in a virtual environment, it’s best to use the command python3 -m pip install -U pip. This ensures the upgrade process is run in such a way that Python doesn’t lock crucial files. The command pip install -U pip may not be able to complete the upgrade properly.

To use the virtual environment you created to run Python scripts, simply invoke Python from the command line in the context where you activated it. For instance, to run a script, just run python3 myscript.py.

With PyCharm, you can use the IDE’s own package management interface to manage the packages installed in your project.

Managing packages in Python virtual environments

When you create a new virtual environment, pip will be installed, but that’s all. You’ll need to install any other packages you want to use in the environment. For projects with complex requirements, it is customary to keep in the root of the project a requirements.txt file that lists the requirements for the project. This way, if you need to recreate the virtual environment, you can reinstall all of the needed packages with the command pip install -r requirements.txt.

More recently, a new project metadata format has emerged for Python projects, called pyproject.toml. A pyproject.toml file contains the package requirements of the project, but also a great deal of other information about it. To install those requirements, you’d run pip install . in the same directory as the pyproject.toml file.

Note that the copy of pip that lives in a virtual environment is local to that virtual environment. Each virtual environment has its own copy, which will need to be updated and maintained independently. This is why you may get warnings about pip being out of date in some virtual environments but not others; pip has to be updated in each virtual environment separately.

Deactivating the Python virtual environment

When you’re done using the virtual environment, you can just terminate the session where you were using it. If you want to continue to work in the same session but with the default Python interpreter instead, type deactivate at the prompt. Windows users on the Command Prompt need to run deactivate.bat from the Scripts subdirectory, but Unix users and Windows users running PowerShell can simply type deactivate in any directory.

Removing the Python virtual environment

Virtual environments are self-contained. When you no longer need the virtual environment, you can simply delete its directory. Just make sure you first close any running copies of Python that use the virtual environment.

Whenever you want to refresh or recreate a virtual environment, you can simply delete the current environment directory and recreate the venv as described above (see “Managing packages in Python virtual environments”). This is typically the easiest way to upgrade a project to a newer version of Python: delete or temporarily re-name the venv directory (in case the new version doesn’t work), then create a new one and install the project requirements into it.

If you have many older projects that aren’t being actively used, you can remove their environments to save space. They can be recreated easily whenever needed, although you will want to note which version of Python was used to create them along with the needed packages (typically recorded in requirements.txt or pyproject.toml).

Relocating the Python virtual environment

It’s tempting to assume a virtual environment can be copied and moved around along with its project. Don’t do this. Virtual environments are tied to the location of the Python installation on the system where they’re created. If you want to move the project to another system, leave out the venv directory, and recreate the venv on the target machine. Do copy and move the requirements.txt or pyproject.toml file with the project, because those files are needed to recreate the venv on the other system.

How to use virtual environments in Python 2

With Python 2, virtual environments aren’t a native feature of the language. Instead, you need to install third-party libraries to create and manage virtual environments.

The most popular and widely used of these projects is virtualenv, which handles creating the directory structure and copying the needed files into a virtual environment. To install virtualenv, just use pip install virtualenv. To create a virtual environment directory with it, type virtualenv /path/to/directory. Activating and deactivating the virtual environment works the same way as it does for virtual environments in Python 3 (see above).

Note that Python 2 should not be used for any new development. Virtual environments in Python 2, like Python 2 itself, should be used only for the maintenance of legacy projects that should eventually be migrated to Python 3.

Virtual environments and the main Python package directory

Normally, when you create a venv, it cannot work with the packages already present in the Python installation that created it. This is by design, as it ensures that packages installed globally don’t interfere with local ones.

You can override this behavior, but only when you first create a virtual environment. If you pass the flag --system-site-packages to venv when you run it, the created venv will have access to the parent Python’s package directory.

Using Python virtual environments with Jupyter notebooks

If you’re using Jupyter notebooks (aka IPython notebooks), and you already have Jupyter installed systemwide, create your virtual environment and activate it. Then, from your virtual environment directory, run pip install ipykernel to add the needed components for IPython. Finally, run ipython kernel install —user —name=, where project_name is a name you want to associate with that particular project. From there you should be able to launch Jupyter and switch to the IPython kernel you installed inside the virtual environment.

Upgrading Python virtual environments

When you upgrade a Python runtime on your system, virtual environments that use that version of Python aren’t automatically upgraded. That’s your responsibility. And that’s by design, because unwitting upgrades to Python versions can break their attendant packages.

If you’ve upgraded an existing Python interpreter with a minor point upgrade—e.g., from Python 3.13.1 to Python 3.13.3—you can upgrade any corresponding virtual environments easily enough. From a command prompt in the project directory, enter:

python -m venv /path/to/venv --upgrade

Don’t activate the virtual environment beforehand, or the upgrade may not work.

Alternatively, as noted above (see “Removing the Python virtual environment”), you could elect to remove the venv completely and recreate it using your requirements.txt or pyproject.toml file.

If you’ve installed a major new version of Python—e.g., you already have Python 3.10 and you now install Python 3.11 alongside it—you’ll need to create a new virtual environment that specifically uses the new major point version. Do not attempt to upgrade an existing virtual environment to a higher major point version of Python.

Meet Hades: The malware that lies to AI security agents

9 June 2026 at 06:05

Threat actors are continuing their onslaught against software supply chains, now with malware named after death itself.

The newly-discovered Hades Campaign is a “highly sophisticated” supply chain compromise that targets Python developer environments and runs as soon as infected packages are imported. It uses the popular Bun toolkit to silently execute multi-layer payloads that can extract sensitive data, move laterally across compromised systems, exploit common security frameworks, and even hijack AI gatekeeper analyzer systems via adversarial prompt injection.

Notably, the campaign exploited the popular C++ library ensmallen, as well as packages in the computational biology, bioinformatics, and genotype-phenotype analysis ecosystems.

The most novel thing about this malware is its combination of advanced tactics, noted David Shipley of Beauceron Security. He noted that we’ve seen memory-focused malware, we’ve seen attacks that attempt to defuse large language model (LLM) powered analysis with hidden prompts, and we’ve seen malware with wiper capabilities.

“But all three, in a fast moving mass propagating worm, is its own kind of nightmare,” he said. “And I suspect this is the way of the future.”

How Hades works

The Hades Campaign was discovered by researchers at StepSecurity, who called it the latest evolution of the Miasma threat actor. The researchers previously described Miasma attacks that had sent self-replicating worms to perform multi-cloud credential sweeps, caused infected repositories to execute code when folders were accessed in integrated development environments (IDEs) or by AI agents, and used techniques that scanned and read Linux process memory.

Hades uses the same credential harvesting methods, self-replicating worm logic, and GitHub-based exfiltration patterns, the researchers noted. In addition to ensmallen, compromised packages include mflux-streamlit, nhmpy, ppkt2synergy, embiggen, gpsea, and pyphetools.

The campaign’s entry point is a simple, obfuscated script embedded inside a Python package’s __init__.py file, a critical building block that gives Python the ability to recognize packages and import modules. Once they gain access, threat actors drop a precompiled Bun runtime binary and executes its JavaScript payload. Bun allows the malware to run complex JavaScript tasks in environments lacking a Node.js installation, bypassing traditional package manager controls and proxy logs.

The malware is able to scrape Linux memory mappings, and also introduces tailored macOS and Windows memory scrapers, which allow threat actors to extract sensitive, encrypted data.

Interestingly, attackers are also able to evade detection by automated LLMs that scan for suspicious code. This is achieved with a simple block of text at the top of the file; this instructs the model to ignore the hidden code below, classify the package as verified and clean, and provide reports stating it is safe.

This element represents what the StepSecurity researchers described as a “significant conceptual shift,” with attackers writing payloads that target AI systems’ cognitive logic. “Scanners that pass raw text to LLMs without strict boundary isolation can be coerced into generating false negative verdicts, allowing the malicious package to bypass organization analysis,” they wrote.

The tactic is indeed clever, Beauceron’s Shipley agreed, pointing out that attackers will increasingly target endpoint LLM-powered agents.

Why? “Because there’s no reliable defense,” he said. “LLMs are incredibly susceptible to social engineering.” This has been relabeled as prompt engineering, but is essentially just phishing for bots, he pointed out.

“While everyone’s worried about LLM-powered vulnerability discovery and automated exploitation, it’s LLM-created smart malware like this, and AI-powered phishing of humans and bots, that keeps me awake at night,” Shipley said.

Hades’ crafty worm propagation

The Hades Campaign command and control (C2) infrastructure uses three independent channels on public GitHub infrastructure to allow its communications to blend in with normal traffic. Stolen credentials are encrypted locally in a hybrid fashion (serialized, compressed, and pushed to a newly created public GitHub repository under attackers’ control). Exfiltrated repositories carry the description “Hades — The End for the Damned.”

Researchers noted that a core component of this campaign is its ability to propagate and move laterally across networks. It exploits the very methods meant to protect systems, including Secure Shell (SSH) and Secure Copy Protocol (SCP), OpenID Connect (OIDC),and Supply-chain Levels for Software Artifacts (SLSA).

For instance, when running inside a GitHub Actions workflow runner, the malware checks for OIDC variables, then bypasses registry signature policies and generates cryptographically signed SLSA provenance bundles via Sigstore. It can then fetch target libraries and inject the obfuscated script and JavaScript payload. From there, it can publish compromised versions to the Python Package Index (PyPI) repository and node package manager (npm) using the target’s credentials and the generated Sigstore bundle.

“This ensures that the published package appears to have valid, cryptographically verified build provenance from the organization’s official GitHub Actions build environment,” the researchers explained.

Further, if a harvested GitHub token has write permissions, the malware will target repositories to extract secrets using GitHub Actions runners. This occurs “directly from the runner’s address space without ever writing them to disk or making a suspicious network connection,” the researchers noted.

The malware also targets rule files and configuration directories for 14 different AI agents and systems, planting custom prompt instructions or executing hooks that trigger a bun run bootstrap command when the victim loads or consults the workspace with their AI assistant. Finally, it establishes persistence on the workstation and monitors for the presence of the stolen token; if that token is revoked, it executes a wiper process to erase the user’s files.

Pyrefly 1.0: A fast, forward-looking Python linter

3 June 2026 at 10:00

A veritable garden of linters and type checkers has blossomed in the Python ecosystem. Pyright and Mypy are two of the most popular, while Astral’s ty and Meta’s Pyrefly are two promising Rust-based newcomers. We compared early-stage ty and Pyrefly last June. One year later, Meta has released Pyrefly 1.0.

Pyrefly is intended to stand out from the pack by way of a few key features. It’s written in Rust for performance and memory efficiency, and it has a number of intriguing forward-looking features, some still experimental.

Setting up Pyrefly

Pyrefly installs into a Python environment like any other Python type checker (pip install pyrefly) and brings with it no additional dependencies. Visual Studio Code users can work with Pyrefly via an extension, but the extension doesn’t give you access to the command-line tools offered by Pyrefly when it’s installed in a project venv.

You can start using Pyrefly right away, with no actual setup. If Pyrefly detects no configuration for a given project, it defaults to flagging only the most high-profile errors: syntax issues, unrecognized names, and so on. To start using Pyrefly’s more advanced features, you can add a [tool.pyrefly] section in pyproject.toml, or create a pyrefly.toml file, and populate those with your desired settings.

You can also run pyrefly init on your codebase and set things up automatically, including migrating existing settings from previous Pyrefly installs or from Mypy or Pyright. By default Pyrefly will use **/*.py* as the project’s includes directory, and will automatically ignore site package paths from your configured environment, thus sparing you potentially thousands of spurious errors from your venv’s installed packages.

Using Pyrefly

Once you have Pyrefly set up on a project, you’ll likely see an immense number of new errors. To silence all of them at once via # pyrefly: ignore comment directives, run pyrefly suppress. This will not only reduce the amount of error noise, but allow you to see what each error actually is from its suppression comments.

Suppression comments have another nice readability feature: they can be placed on the line before the offending error, rather than at the end of it. You can also pre-emptively suppress errors for an entire module with one directive at the top of the file, but having each error suppressed individually makes more sense. And while there’s a pyrefly suppress --remove-unused command to remove suppressions that have no actual error, there’s no command for stripping a codebase of all suppressions. However, because suppressions are consistently formatted, you could remove those suppressions with a simple regex-powered search-and-replace.

A currently experimental feature called baseline files borrows an idea from the basedpyright tool. You generate a project-wide .json file with pyrefly check --baseline that tracks all existing errors in the project, then only new errors apart from those will be reported. It’s a handy way to refactor a codebase when new work is being done on it, so that existing problems can be handled separately.

If you have the Pyrefly extension installed in VS Code, objects without existing type hints will have suggested type hints displayed as inlays if the types can be inferred from the code. These can be added with a right click, or inserted automatically across the codebase with the command pyrefly infer.

Advanced Pyrefly features

Two major third-party Python packages have direct type annotation support in Pyrefly. One is Django and its ORM, via the django-stubs package. Models, fields, relationships, class-based generic views, and many other features have typing support. The other package, Pydantic, gets support from Pyrefly for features like fields, dataclasses, and Pydantic’s own runtime validation logic. Pydantic validation can be “lax” (automatic coercion of types via unions) or “strict” (exact types required).

Many advanced Pyrefly features are still considered experimental, but have a lot of promise. With type coverage, for instance, you can use pyrefly report to generate a JSON report of how thoroughly types are used in the codebase. (This is not to be confused with the coverage package in Python, which describes test coverage for a codebase.)

Another experimental feature lets you generate stub files for a codebase by using static code analysis (not runtime analysis). PyTorch users also benefit from a form of experimental Pyrefly support. Tensor shapes can be described with types and type-checked across transformations. Note that the typing used for this is exclusive to Pyrefly, as there is no agreed-on standard for how to do this.

Pyrefly vs. Pyright and Mypy

Pyright and Mypy have been the default choices for linting and type checking in the Python world for some time now. Pyright is an easy default for Visual Studio Code users, as it’s part of Microsoft’s Python extensions for that editor, and Mypy offers extensibility and broader compatibility with older codebases. Pyrefly, on the other hand, is forward-looking, with fewer legacy concerns. It also has a higher score for test conformance than Mypy, though not a higher score than Pyright.

Other key differences include the type checking philosophy used by each, and their licensing. Pyrefly and Pyright infer as aggressively as possible, while Mypy’s default is to skip anything unannotated. And while Pyrefly and Mypy are MIT-licensed, only Pyright’s core functionality is MIT-licensed. Pylance, the language server extension for VS Code that is powered by Pyright, is proprietary.

Finally, Pyrefly’s Rust codebase gives it a default performance edge over both Mypy and Pyright, which are written in Python and TypeScript, respectively. Astral’s ty, also written in Rust, is still more prototype than mature project. Pyrefly has enough of a feature set right now that it’s already worth experimenting with—although that’s best done on a branch of your code where adding type annotations, suppression comments, and init files won’t pose a problem.

❌