Migration from assertpy¶
assertpy2 is a drop-in, fully typed replacement for the original
assertpy - the same assert_that(...) API, so for most projects
the switch is a single import.
TL;DR
On Python 3.10+, replace from assertpy import ... with from assertpy2 import ... and run your
tests. The assertions you already use carry over unchanged.
Before you start¶
- Python 3.10+ is required. The original assertpy ran on Python 2.7 and 3.x. assertpy2 targets 3.10 through 3.15. If you are on an older interpreter, upgrade Python first - this is the only hard requirement of the migration.
- No runtime dependencies on Python 3.11+. On 3.10 a single tiny backport (
typing_extensions) is pulled in automatically. The extras ([json],[data],[allure],[behave]) stay opt-in.
Switch the import¶
Swap the dependency first - uninstall assertpy, install assertpy2, and update your
requirements/pyproject - then switch the imports:
If you import the module instead of its names, alias it so existing call sites keep working:
A project-wide find-and-replace of from assertpy import to from assertpy2 import is usually the
entire migration.
Automate it across a project
grep -rl --include="*.py" "assertpy" . \
| xargs sed -i 's/from assertpy import/from assertpy2 import/g'
from assertpy import lines. For
import assertpy usages, prefer the alias shown above.
What stays the same¶
assertpy2 is a superset of the original, so the assertions you already use are present and produce the same results:
- the
assert_that()entry point and fluent (return self) chaining - strings, numbers, lists, tuples, dicts, sets, booleans,
None, dates, files, and objects - dynamic
has_<name>()assertions andextracting()withfilterandsort - soft assertions, expected exceptions (
raises().when_called_with()),fail(),assert_warn(), snapshot testing, andadd_extension() - the existing failure messages, apart from the extra detail noted below.
Backward-compatible failures
Failing assertions now raise AssertionFailure, a subclass of AssertionError. Existing
except AssertionError handlers keep working unchanged. The subclass simply carries extra
structured data.
If your tests assert on message text
assertpy2 writes more into some failures than the original did, usually by adding to the end of the
message. A pytest.raises(match=...) or a substring check keeps passing. Comparing a whole message
with == is the one thing that can break, both on the switch and between assertpy2 versions. Prefer
matching a substring, or skip the text entirely and assert on the structured data the failure
carries: AssertionFailure.actual, .expected, and .diff.
What improves automatically¶
You get these the moment you switch, without touching any test code:
- Thread-safe and async-safe soft assertions. State is isolated per thread and per
asyncio.Taskviacontextvars, so soft assertions are safe underpytest-xdistandasyncio. The original's soft assertions were not thread-safe. - Structured failures and rich diffs. Failures carry
.actual/.expected/.diff, and the diff is rendered into the message itself (so it shows inunittest, plain scripts, and CI logs). Under pytest the auto-registered plugin renders it as a colored report section instead - recursive for lists, dicts, dataclasses, namedtuples, attrs classes, and Pydantic models. Setassertpy2_diff = "off"to turn that section off. - Static typing. With
py.typedand@overloadprotocols your editor filters autocomplete by the value's type, and a type checker flags misuse before the tests run - see Type Safety.
What you can now adopt¶
New capabilities the original never had, ready whenever you want them:
- Composable matchers, reusable across assertions and the plain
==form. - Structural matching for API-response shapes.
- Typed narrowing (
.value) and contract testing for typed API responses. - Exception cause chains and groups.
- The collection pipeline and universal negation.
- Async and blocking polling for eventual consistency.
- JSON Path / Schema, regex group extraction, and bytes assertions.
See the comparison for the full feature delta.