Two nondestructive checks fully de-risk a Poetry Python upgrade: parse poetry.lock wheel filenames for target cpXY/abi3 tags (offline), and re-lock a /tmp copy with the new constraint. Poetry 2.x requires poetry env use with a compatible interpreter before it will lock.
When planning a Python minor-version upgrade (e.g. 3.11 to 3.12) for a Poetry project, you can de-risk dependency compatibility almost entirely before editing anything, with two nondestructive checks:
1. Wheel-tag audit — fully offline. poetry.lock already enumerates every wheel filename per package in its files lists. Parse the lock with stdlib tomllib and flag any package that has platform-specific wheels (cpXY tags) but none matching the target (cp312, abi3, py3-none). No network, runs in milliseconds, and covers the exact pinned versions you'd actually install:
import tomllib
lock = tomllib.load(open('poetry.lock', 'rb'))
for pkg in lock['package']:
wheels = [f['file'] for f in pkg.get('files', []) if f['file'].endswith('.whl')]
plat = [w for w in wheels if not w.endswith('-none-any.whl')]
if plat and not any(t in w for w in plat for t in ('cp312', 'abi3', 'py3-none')):
print(pkg['name'], pkg['version'])Zero flagged packages means the current pins install unchanged on the new interpreter.
2. Scratch re-lock — proves resolution without touching the repo. Copy pyproject.toml + poetry.lock into a /tmp dir, symlink any relative path-dependency siblings next to it (so ../sibling resolves), edit the python = ">=3.11,<3.12" constraint to the target range, and run poetry lock. Then diff <(grep -E '^(name|version) = ' old.lock) <(grep -E '^(name|version) = ' new.lock) gives a clean version-churn signal. Packages installed only under python_version < "3.12" markers (e.g. async-timeout) dropping out is expected noise, not churn.
Gotcha: Poetry 2.x refuses to lock when the activated interpreter doesn't satisfy the new constraint — it fails with "The currently activated Python version X is not supported by the project" even though locking is metadata-only. Run poetry env use /path/to/target/python in the scratch dir first (uv-managed interpreters at ~/.local/share/uv/python/ work fine). Clean up afterwards with poetry env remove --all since the scratch dir hashes to a new named venv in the poetry cache.
Together these two checks convert "we think the deps support 3.12" into verified fact: pinned wheels exist for the target ABI, and the resolver accepts the new range with zero version changes.