Repository navigation
fix: ops.testing.Container fallback type checking; ty and pyrefly in ci - #2804
Conversation
ops.testing imports Container from Scenario, falling back to ops.model.Container when Scenario is not installed. ty and pyrefly saw both imports and treated ops.testing.Container as a union, so every Scenario-style testing.Container(...) call in a charm's tests failed to type check. Guard the fallback with TYPE_CHECKING. Runtime behaviour is unchanged. Also type check the example charms with pyrefly and ty in CI, next to mypy. The ty step does not fail the job, as ty is pre-1.0. Fixes canonical#2803
james-garner-canonical
left a comment
There was a problem hiding this comment.
Thanks, a few questions and suggestions.
| # Hidden from type checkers so that they see only scenario.Container, | ||
| # rather than a union of scenario.Container and ops.model.Container. | ||
| if not TYPE_CHECKING: | ||
| from .model import Container |
There was a problem hiding this comment.
This makes sense with how we want this module to be used. I tried to think of a way this could be breaking for someone -- it's a type checking change only, so I guess they'd have to be using testing.Container somewhere that you expect it to be model.Container, expecting the union of testing.Container | model.Container, and the narrowing it to just model.Container, which seems vanishingly unlikely.
Use one dependency group and requirements file per type checker, make ty a blocking step, and drop search paths that pyrefly doesn't need. ty still needs the local Ops and Scenario paths because it doesn't follow the .pth file that uv run uses for --with packages.
|
@tonyandrewmeyer thanks! Split it into separate |
|
@james-garner-canonical thanks for the review!
|
Interesting, I don't think we want the PyPI version of Ops to end up in the charm's venv. Shouldn't
Ah makes sense, I missed that we fetch these in CI. |
|
@james-garner-canonical the charm's own lockfile pins If you'd rather avoid PyPI Ops entirely, I can switch to |
Thanks for explaining, I hadn't realised that that's how
I tested this locally too and I agree that this is the right approach. If you can switch them all to use |
Sync each example charm's venv, then replace its PyPI Ops with the local ops and ops-scenario packages via uv pip install, and run the checkers with uv run --no-sync. ty no longer needs extra search paths to see the local source.
|
@james-garner-canonical done in 5f7a2f0. mypy, pyrefly and ty now each run |
james-garner-canonical
left a comment
There was a problem hiding this comment.
Thanks @Vivek1106-04, this looks good to me.
The new pyrefly dependency group would otherwise fall through to the runtime group (or its own PR for a major version), unlike ty and mypy. Co-Authored-By: Claude Opus 5.5 <[email protected]>
tonyandrewmeyer
left a comment
There was a problem hiding this comment.
Thanks for this. It's unfortunate that we need to do this, but it seems like this will improve things for some cases without making it worse for anyone else. We can fix it properly in 4.0.
This implements items 1 and 5 from @tonyandrewmeyer's type checker gist, limited to the example charms.
The fix
ops/testing.pyimportsContainerfrom Scenario and falls back toops.model.Containerwhen Scenario isn't installed. ty and pyrefly see both imports and treatops.testing.Containerasscenario.Container | ops.model.Container. A Scenario-style call such astesting.Container(name=..., can_connect=True, layers=...)then fails againstops.model.Container.__init__.The fallback is now guarded with
if not TYPE_CHECKING:, so type checkers see onlyscenario.Container. Runtime behaviour is unchanged:ops.testing.Containeris stillscenario.Containerwhen Scenario is installed, andops.model.Containerwhen it isn't.CI
example-charm-tests.yamlnow runs pyrefly and ty after mypy. All three checkers fail the job on errors.uv sync, installs this branch's Ops and Scenario into it withuv pip install ../.. ../../testing, then runs the checker withuv run --no-sync. The mypy step now works the same way.uv run --withlayered the local Ops over the PyPI Ops from the example's lockfile using a.pthfile, which ty doesn't follow, so ty would have checked against the PyPI release rather than this branch.libas an extra search path when it exists (k8s-3, k8s-4 and k8s-5 fetch charm libs in CI), playing the same role aslibinMYPYPATH.pyproject.tomlhas separatemypy,pyrefly(>=1.3) andty(==0.0.85, pinned because it's pre-1.0) dependency groups, each written to its own requirements file by the workflow.dev-toolingDependabot group, alongside ty and mypy.Testing
Run locally with the same commands as the new workflow steps ("before" is without the
ops/testing.pychange):* Only unresolved imports of charm libs, which CI fetches with
charmcraft fetch-libs. I didn't fetch them locally. In CI, all three checkers pass on all seven examples.tox -e lintpasses (pyright 0 errors).tox -e unit -- test/test_testing.pypasses.ops.testing.Container is scenario.Containerwith Scenario installed, andis ops.model.Containerwith Scenario blocked.Fixes #2803.