Skip to content
This repository was archived by the owner on Oct 30, 2026. It is now read-only.

Commit 45fb2c4

Browse files
Merge pull request #3806 from VWS-Python/codex/contract-package-foundation-20261028
Package the shared API contract suite and backend adapters
2 parents 3c29316 + 2fe1760 commit 45fb2c4

328 files changed

Lines changed: 15387 additions & 13486 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
name: Contract package
3+
4+
on:
5+
push:
6+
branches: [main]
7+
pull_request: {}
8+
workflow_dispatch: {}
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
consumer:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v7
18+
with:
19+
persist-credentials: false
20+
- name: Install uv
21+
uses: astral-sh/setup-uv@v10.3.2
22+
- name: Verify installed contracts through the Flask adapter
23+
run: >-
24+
uv run --group=dev pytest
25+
--confcutdir=contract-tests/consumer
26+
-c contract-tests/consumer/pytest.ini
27+
contract-tests/consumer/test_installed_suite.py
28+
env:
29+
UV_PYTHON: '3.15'

‎MANIFEST.in‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
recursive-include docs *.md
2+
include contract-tests/pyproject.toml contract-tests/README.rst contract-tests/LICENSE
3+
recursive-include contract-tests/src *.py py.typed
4+
recursive-include contract-tests/consumer *.py *.ini
5+
include test-backends/pyproject.toml test-backends/README.rst test-backends/LICENSE
6+
recursive-include test-backends/src *.py py.typed

‎conftest.py‎

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,16 @@
22

33
from doctest import ELLIPSIS
44

5-
import pytest
6-
from beartype import beartype
75
from sybil import Sybil
86
from sybil.parsers.rest import (
97
DocTestParser,
108
PythonCodeBlockParser,
119
)
1210

13-
from tests.mock_vws.utils.retries import TRANSIENT_VWS_EXCEPTIONS
14-
1511
pytest_collect_file = Sybil(
1612
parsers=[
1713
DocTestParser(optionflags=ELLIPSIS),
1814
PythonCodeBlockParser(),
1915
],
2016
patterns=["*.rst", "*.py"],
2117
).pytest()
22-
23-
24-
@beartype
25-
@pytest.hookimpl(optionalhook=True)
26-
def pytest_set_filtered_exceptions() -> tuple[type[Exception], ...]:
27-
"""Return exceptions to retry on.
28-
29-
This is for ``pytest-retry``.
30-
The configuration for retries is in ``pyproject.toml``.
31-
32-
``pytest-retry`` treats this as an allowlist: a test which fails
33-
with anything else is not retried, whatever ``retries`` is set to.
34-
That is deliberate, because widening it to ``AssertionError`` would
35-
retry every failing assertion in the suite ten times.
36-
37-
The consequence is that a test which turns a response into an
38-
``AssertionError`` itself, rather than letting an exception out of
39-
the client, is never retried here. The Model Target Web API tests
40-
assert on ``requests.Response`` objects and so are in that
41-
position; they get their own, narrower policy, applied before the
42-
assertion, in
43-
:py:mod:`tests.mock_vws.utils.model_target_retries`.
44-
"""
45-
return TRANSIENT_VWS_EXCEPTIONS

‎contract-tests/LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Adam Dangoor
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎contract-tests/README.rst‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
VWS mock contract tests
2+
=======================
3+
4+
This project contains shared fixtures, helpers and contracts for the Vuforia Web Services and Model Target APIs.
5+
It is a development dependency for projects which implement the Vuforia APIs.
6+
7+
Build and install
8+
-----------------
9+
10+
Build all workspace wheels from the repository root:
11+
12+
.. code-block:: console
13+
14+
$ uv build --all-packages --wheel --out-dir dist
15+
$ uv pip install dist/*.whl
16+
17+
The initial package version is ``0.0.0``.
18+
Install the suite, core library and built-in adapter package from the same repository commit.
19+
For Git installations, use the ``contract-tests`` and ``test-backends`` subdirectories at that commit.
20+
21+
Run the installed suite from outside the source checkout:
22+
23+
.. code-block:: console
24+
25+
$ python -m pytest \
26+
-p vws_mock_contract_tests.fixtures.plugin \
27+
-p vws_mock_test_backends.vuforia_backends \
28+
--pyargs vws_mock_contract_tests \
29+
--skip-backend=real
30+
31+
The shared plugin provides clients, prepared requests and the retry allowlist.
32+
The adapter plugin supplies service URLs, credentials, capabilities and setup fixtures.
33+
Public setup fixtures yield ``None`` once the service is ready and keep backend selection internal.
34+
Another repository can supply its own explicitly loaded adapter plugin.
35+
The shared package has no dependency on the mock implementation or Flask.
36+
37+
Core CI compatibility
38+
---------------------
39+
40+
Import-only modules under ``tests/mock_vws`` preserve the existing CI selectors.
41+
The assertions have one source in ``vws_mock_contract_tests``.
42+
Core coverage measures the packaged source and omits the forwarding modules.
43+
The root test configuration avoids collecting the packaged tests a second time.
44+
45+
The consumer integration test builds wheels and checks them in a fresh environment:
46+
47+
.. code-block:: console
48+
49+
$ uv run --group=dev pytest \
50+
--confcutdir=contract-tests/consumer \
51+
-c contract-tests/consumer/pytest.ini \
52+
contract-tests/consumer/test_installed_suite.py
53+
54+
The consumer's dependency and lifecycle checks are explicit pytest plugins.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Integration checks for an independent package consumer."""
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Check the installed suite's adapter and import boundaries."""
2+
3+
import sys
4+
5+
import pytest
6+
7+
__all__ = ["pytest_sessionfinish"]
8+
9+
10+
@pytest.fixture(autouse=True)
11+
def ready(request: pytest.FixtureRequest) -> None:
12+
"""Check that public setup fixtures yield None."""
13+
for name in ("verify_mock_vuforia", "mock_only_vuforia"):
14+
if name in request.fixturenames:
15+
assert request.getfixturevalue(argname=name) is None
16+
17+
18+
def pytest_sessionfinish() -> None:
19+
"""Keep the core repository's tests outside the consumer process."""
20+
assert not any(name.startswith("tests.mock_vws") for name in sys.modules)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Check that collecting the shared suite needs no implementation."""
2+
3+
import importlib.util
4+
import sys
5+
6+
__all__ = ["pytest_sessionstart"]
7+
8+
9+
def pytest_sessionstart() -> None:
10+
"""Reject implementation dependencies before collection begins."""
11+
assert importlib.util.find_spec(name="mock_vws") is None
12+
assert importlib.util.find_spec(name="flask") is None
13+
assert not any(name.startswith("tests.mock_vws") for name in sys.modules)

‎contract-tests/consumer/pytest.ini‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[pytest]
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""Exercise built wheels from a separate consumer environment."""
2+
3+
import os
4+
import shutil
5+
import subprocess
6+
import sys
7+
from pathlib import Path
8+
9+
import pytest
10+
11+
12+
def _run(*, args: list[str | Path], cwd: Path) -> None:
13+
"""Run a local tool, failing the test if it fails."""
14+
# Commands use a local executable and fixed options or built wheel paths.
15+
_ = subprocess.run(args=args, cwd=cwd, check=True) # noqa: S603
16+
17+
18+
def test_installed_suite(
19+
*, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
20+
) -> None:
21+
"""Collect without an implementation, then run the Flask adapter."""
22+
consumer = Path(__file__).resolve().parent
23+
repository = consumer.parent.parent
24+
dist = tmp_path / "dist"
25+
environment = tmp_path / "venv"
26+
uv = shutil.which(cmd="uv")
27+
assert uv is not None
28+
monkeypatch.delenv(name="PYTHONPATH", raising=False)
29+
30+
_run(
31+
args=[uv, "build", "--all-packages", "--wheel", "--out-dir", dist],
32+
cwd=repository,
33+
)
34+
_run(
35+
args=[uv, "venv", "--python", sys.executable, environment],
36+
cwd=tmp_path,
37+
)
38+
python = environment / (
39+
"Scripts/python.exe" if sys.platform == "win32" else "bin/python"
40+
)
41+
suite = next(dist.glob(pattern="vws_mock_contract_tests-*.whl"))
42+
_run(args=[uv, "pip", "install", "--python", python, suite], cwd=tmp_path)
43+
for filename in (
44+
"pytest.ini",
45+
"dependency_checks.py",
46+
"contract_checks.py",
47+
):
48+
_ = shutil.copyfile(src=consumer / filename, dst=tmp_path / filename)
49+
50+
_run(
51+
args=[
52+
python,
53+
"-m",
54+
"pytest",
55+
"-p",
56+
"dependency_checks",
57+
"-p",
58+
"vws_mock_contract_tests.fixtures.plugin",
59+
"--pyargs",
60+
"vws_mock_contract_tests",
61+
"--collect-only",
62+
"-q",
63+
],
64+
cwd=tmp_path,
65+
)
66+
_run(
67+
args=[
68+
uv,
69+
"pip",
70+
"install",
71+
"--python",
72+
python,
73+
*dist.glob(pattern="*.whl"),
74+
],
75+
cwd=tmp_path,
76+
)
77+
_run(
78+
args=[
79+
python,
80+
"-m",
81+
"pytest",
82+
"-p",
83+
"contract_checks",
84+
"-p",
85+
"vws_mock_contract_tests.fixtures.plugin",
86+
"-p",
87+
"vws_mock_test_backends.vuforia_backends",
88+
"--pyargs",
89+
os.environ.get(
90+
key="CONTRACT_TEST_TARGET",
91+
default="vws_mock_contract_tests.database_summary",
92+
),
93+
"--skip-backend=real",
94+
"--skip-backend=mock",
95+
"-q",
96+
],
97+
cwd=tmp_path,
98+
)

0 commit comments

Comments
 (0)