<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sean Gillies (Posts about python)</title><link>https://sgillies.net/</link><description></description><atom:link href="https://sgillies.net/tags/python.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><lastBuildDate>Wed, 14 Jan 2026 03:13:25 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Rasterio 1.5.0</title><link>https://sgillies.net/2026/01/13/rasterio-1-5-0.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;Version 1.5.0 of your favorite Python library for reading and writing classic
GIS raster data is on PyPI now. Since Jan 5, in fact.&lt;/p&gt;
&lt;p&gt;Among other new features, this version adds support for 16-bit floating point
raster data, and HTTP cache control. Please See the &lt;a class="reference external" href="https://github.com/rasterio/rasterio/releases/tag/1.5.0"&gt;release notes&lt;/a&gt; for a full list of
bug fixes, new features, and other changes.&lt;/p&gt;
&lt;p&gt;Once again, major credit goes to Alan Snow for managing this release. Thanks, Alan!&lt;/p&gt;</description><category>python</category><category>rasterio</category><category>work</category><guid>https://sgillies.net/2026/01/13/rasterio-1-5-0.html</guid><pubDate>Wed, 14 Jan 2026 02:59:08 GMT</pubDate></item><item><title>Rasterio 1.4.4</title><link>https://sgillies.net/2025/12/12/rasterio-1-4-4.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;Rasterio 1.4.4 is on PyPI now. The first release in a year and ten days. If
you're using Rasterio in 2025, shoot some thanks to Alan Snow, aka @snowman2 on
Github. He's leading the effort to get Rasterio caught up to recent GDAL and
Python changes, and it's not a cakewalk!&lt;/p&gt;
&lt;p&gt;Release notes are here:
&lt;a class="reference external" href="https://github.com/rasterio/rasterio/releases/tag/1.4.4"&gt;https://github.com/rasterio/rasterio/releases/tag/1.4.4&lt;/a&gt;. That's a lot of bug
fixes.&lt;/p&gt;</description><category>python</category><category>rasterio</category><category>work</category><guid>https://sgillies.net/2025/12/12/rasterio-1-4-4.html</guid><pubDate>Sat, 13 Dec 2025 03:13:52 GMT</pubDate></item><item><title>Rasterio 1.4.4rc0</title><link>https://sgillies.net/2025/12/06/rasterio-1-4-4-rc0.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;I've got good news for people who love news about Rasterio, the Python package
for reading and writing classic GIS raster data. Alan Snow is the &lt;a class="reference external" href="https://github.com/rasterio/rasterio/discussions/3435"&gt;release
manager for 1.4.4 and 1.5.0&lt;/a&gt; and has shepherded a release candidate with 40 wheels and one source
distribution onto the Python Package Index: &lt;a class="reference external" href="https://pypi.org/project/rasterio/1.4.4rc0/"&gt;https://pypi.org/project/rasterio/1.4.4rc0/&lt;/a&gt;. The release notes are &lt;a class="reference external" href="https://github.com/rasterio/rasterio/releases/tag/1.4.4rc0"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Please try these out and let us know if they work as expected.&lt;/p&gt;</description><category>python</category><category>rasterio</category><category>work</category><guid>https://sgillies.net/2025/12/06/rasterio-1-4-4-rc0.html</guid><pubDate>Sun, 07 Dec 2025 01:38:37 GMT</pubDate></item><item><title>Python typing mulligan</title><link>https://sgillies.net/2024/11/17/python-typing-mulligan.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;This is why I've been hesitant to add type hints to Fiona, Rasterio, and
Shapely. David Lord on &lt;a class="reference external" href="https://mas.to/@davidism/113489750103116039"&gt;missteps and misgivings&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I want a "start over" tool for type annotating a Python library. I started
with Flask as untyped code, then added annotations until mypy stopped
complaining. But this didn't mean the annotations were _correct_. Over time
I've fixed various reported issues. I feel like if I could start from
scratch again, I'd probably get closer to correct with the experience I've
gained. But removing all existing annotations and ignores is too time
consuming on its own. #python&lt;/p&gt;
&lt;/blockquote&gt;</description><category>open source</category><category>python</category><category>typing</category><category>work</category><guid>https://sgillies.net/2024/11/17/python-typing-mulligan.html</guid><pubDate>Mon, 18 Nov 2024 02:16:36 GMT</pubDate></item><item><title>Use pytest's tmp_path fixture</title><link>https://sgillies.net/2024/11/05/use-pytest-tmp-path-fixture.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;If you're not already using pytest's &lt;code class="docutils literal"&gt;tmp_path&lt;/code&gt; &lt;a class="reference external" href="https://docs.pytest.org/en/stable/how-to/tmp_path.html"&gt;fixture&lt;/a&gt;, you really should.
The fixture provides a temporary directory for testing use. A directory that
you can't reference from other test runs or from other test functions in the
same run, but that isn't immediately deleted when your tests finish. The
directory is created in your account temporary location and is eventually
cleaned up by your computer's operating system. Until then, you can open the
directories and their files in other applications.&lt;/p&gt;
&lt;p&gt;I've been making assertions on datasets in Rasterio's tests and also dragging
them into QGIS for a closer look after the tests finish.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="https://live.staticflickr.com/65535/54120218694_cd56609f30_b.jpg" src="https://live.staticflickr.com/65535/54120218694_cd56609f30_b.jpg"&gt;
&lt;/figure&gt;</description><category>programming</category><category>pytest</category><category>python</category><category>qgis</category><category>work</category><guid>https://sgillies.net/2024/11/05/use-pytest-tmp-path-fixture.html</guid><pubDate>Tue, 05 Nov 2024 23:56:14 GMT</pubDate></item><item><title>Fiona 1.10.0</title><link>https://sgillies.net/2024/09/10/fiona-1-10-0.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;I made a software release last Tuesday. One with new features, deprecations,
major refactoring, and packaging and dependency changes, not just bug fixes.
&lt;a class="reference external" href="https://github.com/Toblerity/Fiona/releases/tag/1.10.0"&gt;Fiona 1.10.0&lt;/a&gt; has
three main changes: Python openers, CLI filter commands, and new data classes.&lt;/p&gt;
&lt;p&gt;Python openers can connect filesystems implemented in Python, like fsspec or
tiledb.vfs, to GDAL's own virtual filesystem machinery. In most cases, you
should reply on GDAL's built-in virtual filesystem handlers. On the other hand,
if you have unique or proprietary data access protocols, then Fiona's new
openers may be useful. As far as I know, Fiona (and Rasterio) have the only
open source implementations of GDAL's virtual filesystem plugin system. David
Hoese had the initial idea, Even Rouault helped a lot, and I got it over the
finish line. I think this is right up there with &lt;a class="reference external" href="https://fiona.readthedocs.io/en/stable/manual.html#memoryfile-and-zipmemoryfile"&gt;MemoryFile&lt;/a&gt;
for my favorite feature that didn't exist in Python-GIS software before.&lt;/p&gt;
&lt;p&gt;Fiona's CLI has three new commands, filter (strictly speaking, a new mode of
this command), map, and reduce. These provide some great features for
Unix-style data processing pipelines and are designed to work well with jq and
programs of that nature. Think of them as the data processing part of ogr2ogr,
split into 3 simpler commands, reading and writing to stdin/stdout by default,
with no SQL and no need to know about different SQL dialects. The documentation
contains a new tutorial about &lt;a class="reference external" href="https://fiona.readthedocs.io/en/stable/cli.html#sizing-up-and-simplifying-shapes"&gt;using filter, map, and reduce&lt;/a&gt;.
This work began in &lt;a class="reference external" href="https://github.com/planetlabs/fio-planet"&gt;planetlabs/fio-planet&lt;/a&gt; and now lives in the Fiona CLI
core. Thank you, Tim Schaub, for stewarding the transition.&lt;/p&gt;
&lt;p&gt;Lastly, Fiona now longer represents GIS features (and their geometries and
properties) as Python dicts, but as Python classes: &lt;code class="docutils literal"&gt;fiona.model.Feature&lt;/code&gt;,
&lt;code class="docutils literal"&gt;fiona.model.Geometry&lt;/code&gt;, and &lt;code class="docutils literal"&gt;fiona.model.Properties&lt;/code&gt;. These classes provide
dict-like access for backwards compatibility, but raise warnings when mutated.
These data classes will be immutable in version 2.0.&lt;/p&gt;
&lt;p&gt;A lot of GIS-Python attention has moved on to columnar data and massive amounts
of time series, trajectories, telemetry, etc, using Parquet and Arrow. But,
there's still a need to reason about persistent spatial things in our world and
their relationships to each other. Classic GIS features, in other words.
Watersheds, counties, neighborhoods. That's what Fiona remains concerned about.&lt;/p&gt;</description><category>fiona</category><category>python</category><category>work</category><guid>https://sgillies.net/2024/09/10/fiona-1-10-0.html</guid><pubDate>Wed, 11 Sep 2024 01:16:41 GMT</pubDate></item><item><title>New home for Rasterio</title><link>https://sgillies.net/2021/10/26/new-home-for-rasterio.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;I've announced the move elsewhere, but have been late to mention it here.
Rasterio has been transferred from &lt;a class="reference external" href="https://github.com/mapbox"&gt;https://github.com/mapbox&lt;/a&gt; to
&lt;a class="reference external" href="https://github.com/rasterio"&gt;https://github.com/rasterio&lt;/a&gt;. Today I transferred the affine project, which
rasterio depends on, from my own personal repos to the same rasterio org.&lt;/p&gt;
&lt;p&gt;Before I left Mapbox, about 45% of the project's commits were authored by folks
outside the company. Now, that number is closer to 90%. Moving the project
keeps the code open and accessible to its developers and makes it easier to
maintain. Making the case for the move at work was pretty easy. Teams at Mapbox
that use rasterio want bug fixes and new releases. The Platform team has one
less oddball CI configuration to support. I'm grateful for the attention and
help of Mapbox's Legal and IT teams during a busy, busy time. It was fun to
work with these folks on one last project.&lt;/p&gt;
&lt;p&gt;I'm planning to stay very involved in these projects. Go and try the new 1.3
pre-release and report back if you find any new bugs.&lt;/p&gt;</description><category>affine</category><category>gdal</category><category>python</category><category>rasterio</category><category>work</category><guid>https://sgillies.net/2021/10/26/new-home-for-rasterio.html</guid><pubDate>Tue, 26 Oct 2021 16:02:10 GMT</pubDate></item><item><title>Shapely 1.8.0</title><link>https://sgillies.net/2021/10/26/shapely-1-8-0.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;From the 1.8.0 &lt;a class="reference external" href="https://github.com/Toblerity/Shapely/releases/tag/1.8.0"&gt;release notes&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Shapely 1.8.0 is a transitional version. There are a few bug fixes and new
features, but it largely exists to warn about the upcoming changes in
2.0.0. See the new migration guide for more details on how to update your
code from Shapely 1.8 to 2.0.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the upcoming changes: the geometry objects of 2.0 will be immutable,
simplifying their implementation, and making it possible to hash them.
Here's an example of the warning you'll see when you set a new attribute on
a version 1.8 geometry object.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code pycon"&gt;&lt;a id="rest_code_6a3f141e2ef04d379b86ee931f93820e-1" name="rest_code_6a3f141e2ef04d379b86ee931f93820e-1" href="https://sgillies.net/2021/10/26/shapely-1-8-0.html#rest_code_6a3f141e2ef04d379b86ee931f93820e-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;shapely.geometry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;
&lt;a id="rest_code_6a3f141e2ef04d379b86ee931f93820e-2" name="rest_code_6a3f141e2ef04d379b86ee931f93820e-2" href="https://sgillies.net/2021/10/26/shapely-1-8-0.html#rest_code_6a3f141e2ef04d379b86ee931f93820e-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_6a3f141e2ef04d379b86ee931f93820e-3" name="rest_code_6a3f141e2ef04d379b86ee931f93820e-3" href="https://sgillies.net/2021/10/26/shapely-1-8-0.html#rest_code_6a3f141e2ef04d379b86ee931f93820e-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_6a3f141e2ef04d379b86ee931f93820e-4" name="rest_code_6a3f141e2ef04d379b86ee931f93820e-4" href="https://sgillies.net/2021/10/26/shapely-1-8-0.html#rest_code_6a3f141e2ef04d379b86ee931f93820e-4"&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;lt;stdin&amp;gt;:1: ShapelyDeprecationWarning: Setting custom attributes on geometry objects is deprecated, and will raise an AttributeError in Shapely 2.0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</description><category>geometry</category><category>geos</category><category>gis</category><category>python</category><category>shapely</category><category>work</category><guid>https://sgillies.net/2021/10/26/shapely-1-8-0.html</guid><pubDate>Tue, 26 Oct 2021 14:29:27 GMT</pubDate></item><item><title>Shapely 2.0 roadmap</title><link>https://sgillies.net/2020/05/02/shapely-2-0-roadmap.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;There's going to be a 2.0 version of shapely. Joris Van den Bossche has written
an &lt;a class="reference external" href="https://github.com/shapely/shapely-rfc/pull/1"&gt;RFC for a roadmap&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Joris is a Pandas and GeoPandas developer. The GeoPandas team has identified
some enhancements to shapely that would allow GeoPandas and shapely to reach
new levels of performance, and we're going to implement them. A &lt;a class="reference external" href="https://numfocus.org/"&gt;NumFOCUS&lt;/a&gt; grant will support some of this work.&lt;/p&gt;
&lt;p&gt;I'm super late in blogging about this and commenting on the RFC, which has lead
to a little confusion about whether I'm in favor of this. I am, 100 percent.
Rewrites aren't easy, but this effort has a lot going for it: serious use
cases, committed stakeholders, clever and pragmatic programmers, funding, and
lots of goodwill. We're going to have a 2.0 version that removes cruft, adds
killer new features, and isn't super difficult to migrate to.&lt;/p&gt;
&lt;p&gt;Please read the RFC pull request and comment if you're into helping shape
the future of Python's #1 open source GIS geometry package.&lt;/p&gt;</description><category>geopandas</category><category>python</category><category>shapely</category><category>work</category><guid>https://sgillies.net/2020/05/02/shapely-2-0-roadmap.html</guid><pubDate>Sat, 02 May 2020 22:25:34 GMT</pubDate></item><item><title>pytest.raises excinfo subtlety</title><link>https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html</link><dc:creator>Sean Gillies</dc:creator><description>&lt;p&gt;In the past few weeks I've seen multiple stumbles related to a subtlety of
pytest.  I'm going to explain how to recognize the issue and what to do about
it.&lt;/p&gt;
&lt;p&gt;Exceptions are an aspect of a Python package's API, just like the names of the
functions, their parameters, and their return types. They also require testing.
Pytest provides a handy context manager for this: &lt;a class="reference external" href="https://docs.pytest.org/en/latest/reference.html#pytest.raises"&gt;pytest.raises&lt;/a&gt;.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-1" name="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-1" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;accept_numbers_lt_3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-2" name="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-2" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-2"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-3" name="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-3" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-3"&gt;&lt;/a&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Number is not less than 3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-4" name="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-4" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-5" name="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-5" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-5"&gt;&lt;/a&gt;
&lt;a id="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-6" name="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-6" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-6"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_unaccepted_error&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;a id="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-7" name="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-7" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-7"&gt;&lt;/a&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-8" name="rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-8" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_4f7f8a4d4c5a485e976c6bd7528a41f0-8"&gt;&lt;/a&gt;        &lt;span class="n"&gt;accept_numbers_lt_3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That test will pass. The function does raise a ValueError when called with the
argument 3.&lt;/p&gt;
&lt;p&gt;To make assertions about details of the exception you might try the following.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_2730e636745f41068a8321064f6ed451-1" name="rest_code_2730e636745f41068a8321064f6ed451-1" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_2730e636745f41068a8321064f6ed451-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_unaccepted_error_msg&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;a id="rest_code_2730e636745f41068a8321064f6ed451-2" name="rest_code_2730e636745f41068a8321064f6ed451-2" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_2730e636745f41068a8321064f6ed451-2"&gt;&lt;/a&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;excinfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_2730e636745f41068a8321064f6ed451-3" name="rest_code_2730e636745f41068a8321064f6ed451-3" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_2730e636745f41068a8321064f6ed451-3"&gt;&lt;/a&gt;        &lt;span class="n"&gt;accept_numbers_lt_3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_2730e636745f41068a8321064f6ed451-4" name="rest_code_2730e636745f41068a8321064f6ed451-4" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_2730e636745f41068a8321064f6ed451-4"&gt;&lt;/a&gt;        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;excinfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Number is not less than three"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This test passes too. But wait, we mistyped the expected string. We're
asserting that the message ends with "three" and that can't be true, can it?
How did this test pass?&lt;/p&gt;
&lt;p&gt;Here's the important thing: pytest "magically" changes the interpretation of
assert statements, but it doesn't change the behavior of Python's "with"
statements.  That final assert statement in test_unaccepted_error_msg is never
reached. Execution exits from the block when the ValueError (or any other
exception) is raised.&lt;/p&gt;
&lt;p&gt;The excinfo object has recorded the captured exception so that we can
inspect it. We only need to move that assert statement to after the with block.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_367d42a5be2c4c278060eebd0ea51dfc-1" name="rest_code_367d42a5be2c4c278060eebd0ea51dfc-1" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_367d42a5be2c4c278060eebd0ea51dfc-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_unaccepted_error_msg&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;a id="rest_code_367d42a5be2c4c278060eebd0ea51dfc-2" name="rest_code_367d42a5be2c4c278060eebd0ea51dfc-2" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_367d42a5be2c4c278060eebd0ea51dfc-2"&gt;&lt;/a&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;excinfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_367d42a5be2c4c278060eebd0ea51dfc-3" name="rest_code_367d42a5be2c4c278060eebd0ea51dfc-3" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_367d42a5be2c4c278060eebd0ea51dfc-3"&gt;&lt;/a&gt;        &lt;span class="n"&gt;accept_numbers_lt_3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_367d42a5be2c4c278060eebd0ea51dfc-4" name="rest_code_367d42a5be2c4c278060eebd0ea51dfc-4" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_367d42a5be2c4c278060eebd0ea51dfc-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_367d42a5be2c4c278060eebd0ea51dfc-5" name="rest_code_367d42a5be2c4c278060eebd0ea51dfc-5" href="https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html#rest_code_367d42a5be2c4c278060eebd0ea51dfc-5"&gt;&lt;/a&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;excinfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Number is not less than three"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now this test will fail, properly. And we can change "three" to "3" and have
a passing test of an exception message.&lt;/p&gt;
&lt;p&gt;This issue is documented in a note in the pytest.raises docs, but is easy to
overlook.&lt;/p&gt;</description><category>exceptions</category><category>pytest</category><category>python</category><category>testing</category><category>work</category><guid>https://sgillies.net/2020/03/30/pytest-raises-excinfo-subtlety.html</guid><pubDate>Tue, 31 Mar 2020 01:45:36 GMT</pubDate></item></channel></rss>