Skip to content

Commit 9a7206d

Browse files
committed
fix(deps): bound the core dependency above
Both plugin packages required aws-durable-execution-sdk-python>=3.0.0 with no ceiling. The lower bound exists because their entry points resolve to factories, which core 2.x cannot call: pip accepts the resolution and the handler fails at initialization. Without a ceiling the next core major that changes the plugin contract reproduces that exactly. Both are now >=3.0.0,<4, and each package's metadata test asserts the specifier rejects the next core major as well as accepting the current one. A second case asserts it still admits the next core patch, because a <= ceiling looks equivalent and excludes it.
1 parent aeea6ff commit 9a7206d

5 files changed

Lines changed: 104 additions & 8 deletions

File tree

packages/aws-durable-execution-sdk-python-insight/pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ classifiers = [
2121
"Programming Language :: Python :: Implementation :: CPython",
2222
]
2323
dependencies = [
24-
# >=3.0.0: the first release whose `plugins` argument takes factories.
24+
# >=3.0.0,<4: 3.0.0 is the first release whose `plugins` argument takes
25+
# factories, and the ceiling is the same reasoning applied forwards -- the next
26+
# core major that changes the plugin contract would install and then fail at
27+
# handler initialization exactly as 2.x does below.
2528
# `workflow_insight()` returns a factory, which core 2.x cannot call, so an
2629
# install resolved against 2.x fails at handler initialization. 3.0.0 also
2730
# carries the invocation-hook fields this plugin reads
2831
# (InvocationInfo.execution_input / InvocationEndInfo.execution_result).
29-
"aws-durable-execution-sdk-python>=3.0.0",
32+
"aws-durable-execution-sdk-python>=3.0.0,<4",
3033
]
3134

3235
[project.optional-dependencies]

packages/aws-durable-execution-sdk-python-insight/tests/test_package_metadata.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import tomllib
1717
from pathlib import Path
1818

19+
from packaging.specifiers import SpecifierSet
1920
from packaging.version import Version
2021

2122

@@ -48,7 +49,7 @@ def _core_dependency_lower_bound() -> str:
4849
dependencies = tomllib.load(pyproject)["project"]["dependencies"]
4950

5051
bounds = [
51-
dependency.removeprefix(CORE_DISTRIBUTION + ">=")
52+
dependency.removeprefix(CORE_DISTRIBUTION + ">=").split(",", 1)[0]
5253
for dependency in dependencies
5354
if dependency.startswith(CORE_DISTRIBUTION + ">=")
5455
]
@@ -76,3 +77,46 @@ def test_core_dependency_bound_matches_the_core_major_in_this_repository() -> No
7677

7778
assert _major(lower_bound) == _major(core_version)
7879
assert Version(lower_bound) <= Version(core_version)
80+
81+
82+
def test_core_dependency_excludes_the_next_core_major() -> None:
83+
"""A lower bound alone is the same defect one major later.
84+
85+
The lower bound exists because this package's entry points resolve to plugin
86+
factories, which the core major below cannot call: pip accepts the resolution
87+
and the handler fails at initialization. Without a ceiling the next core major
88+
that changes the plugin contract reproduces exactly that, so the specifier has
89+
to reject it rather than only reject what came before.
90+
"""
91+
with (PACKAGE_ROOT / "pyproject.toml").open("rb") as pyproject:
92+
dependencies = tomllib.load(pyproject)["project"]["dependencies"]
93+
specifiers = [
94+
SpecifierSet(dependency.removeprefix(CORE_DISTRIBUTION))
95+
for dependency in dependencies
96+
if dependency.startswith(CORE_DISTRIBUTION)
97+
]
98+
assert len(specifiers) == 1
99+
100+
core_major = _major(_core_version())
101+
assert specifiers[0].contains(_core_version(), prereleases=True)
102+
assert not specifiers[0].contains(f"{core_major + 1}.0.0", prereleases=True)
103+
104+
105+
def test_core_dependency_admits_a_later_core_patch() -> None:
106+
"""The ceiling belongs on the major, not on the version built here.
107+
108+
A ``<=`` ceiling looks equivalent and is not: it excludes the next core patch,
109+
so the first core patch release puts this claim out of date for a change that
110+
cannot have touched the plugin contract.
111+
"""
112+
with (PACKAGE_ROOT / "pyproject.toml").open("rb") as pyproject:
113+
dependencies = tomllib.load(pyproject)["project"]["dependencies"]
114+
specifier = next(
115+
SpecifierSet(dependency.removeprefix(CORE_DISTRIBUTION))
116+
for dependency in dependencies
117+
if dependency.startswith(CORE_DISTRIBUTION)
118+
)
119+
120+
core = Version(_core_version())
121+
next_patch = f"{core.major}.{core.minor}.{core.micro + 1}"
122+
assert specifier.contains(next_patch, prereleases=True)

packages/aws-durable-execution-sdk-python-otel/pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ classifiers = [
2222
"Programming Language :: Python :: Implementation :: PyPy",
2323
]
2424
dependencies = [
25-
# >=3.0.0: the first release whose `plugins` argument takes factories.
25+
# >=3.0.0,<4: 3.0.0 is the first release whose `plugins` argument takes
26+
# factories, and the ceiling is the same reasoning applied forwards -- the next
27+
# core major that changes the plugin contract would install and then fail at
28+
# handler initialization exactly as 2.x does below.
2629
# DurableInstrumentationPluginProvider was removed in 3.0.0, and this package's
2730
# entry points resolve to factories, so core 2.x accepts the install and then
2831
# fails at handler initialization.
29-
"aws-durable-execution-sdk-python>=3.0.0",
32+
"aws-durable-execution-sdk-python>=3.0.0,<4",
3033
]
3134

3235
[project.entry-points."aws_durable_execution.plugins"]

packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
import tomllib
33
from pathlib import Path
44

5+
from packaging.specifiers import SpecifierSet
56
from packaging.version import Version
67

78

89
PACKAGE_ROOT = Path(__file__).resolve().parents[1]
910
REPOSITORY_ROOT = PACKAGE_ROOT.parents[1]
1011
CORE_DISTRIBUTION = "aws-durable-execution-sdk-python"
11-
CORE_DEPENDENCY = "aws-durable-execution-sdk-python>=3.0.0"
12+
CORE_DEPENDENCY = "aws-durable-execution-sdk-python>=3.0.0,<4"
1213
TEST_OTEL_DEPENDENCIES = {
1314
"opentelemetry-sdk>=1.20.0",
1415
"opentelemetry-propagator-aws-xray",
@@ -112,7 +113,7 @@ def _core_dependency_lower_bound() -> str:
112113
"dependencies"
113114
]
114115
bounds = [
115-
dependency.removeprefix(CORE_DISTRIBUTION + ">=")
116+
dependency.removeprefix(CORE_DISTRIBUTION + ">=").split(",", 1)[0]
116117
for dependency in dependencies
117118
if dependency.startswith(CORE_DISTRIBUTION + ">=")
118119
]
@@ -157,3 +158,48 @@ def test_layer_sdk_pin_matches_the_core_version_in_this_repository() -> None:
157158
pinned_version = tomllib.load(metadata_file)["layer"]["sdk-version"]
158159

159160
assert pinned_version == _core_version()
161+
162+
163+
def test_core_dependency_excludes_the_next_core_major() -> None:
164+
"""A lower bound alone is the same defect one major later.
165+
166+
The lower bound exists because this package's entry points resolve to plugin
167+
factories, which the core major below cannot call: pip accepts the resolution
168+
and the handler fails at initialization. Without a ceiling the next core major
169+
that changes the plugin contract reproduces exactly that, so the specifier has
170+
to reject it rather than only reject what came before.
171+
"""
172+
dependencies = _load_pyproject(PACKAGE_ROOT / "pyproject.toml")["project"][
173+
"dependencies"
174+
]
175+
specifiers = [
176+
SpecifierSet(dependency.removeprefix(CORE_DISTRIBUTION))
177+
for dependency in dependencies
178+
if dependency.startswith(CORE_DISTRIBUTION)
179+
]
180+
assert len(specifiers) == 1
181+
182+
core_major = _major(_core_version())
183+
assert specifiers[0].contains(_core_version(), prereleases=True)
184+
assert not specifiers[0].contains(f"{core_major + 1}.0.0", prereleases=True)
185+
186+
187+
def test_core_dependency_admits_a_later_core_patch() -> None:
188+
"""The ceiling belongs on the major, not on the version built here.
189+
190+
A ``<=`` ceiling looks equivalent and is not: it excludes the next core patch,
191+
so the first core patch release puts this claim out of date for a change that
192+
cannot have touched the plugin contract.
193+
"""
194+
dependencies = _load_pyproject(PACKAGE_ROOT / "pyproject.toml")["project"][
195+
"dependencies"
196+
]
197+
specifier = next(
198+
SpecifierSet(dependency.removeprefix(CORE_DISTRIBUTION))
199+
for dependency in dependencies
200+
if dependency.startswith(CORE_DISTRIBUTION)
201+
)
202+
203+
core = Version(_core_version())
204+
next_patch = f"{core.major}.{core.minor}.{core.micro + 1}"
205+
assert specifier.contains(next_patch, prereleases=True)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ test = "pytest packages/aws-durable-execution-sdk-python-examples/test {args}"
129129

130130
[tool.hatch.envs.test-pypi-otel]
131131
dependencies = [
132-
"aws-durable-execution-sdk-python>=3.0.0",
132+
"aws-durable-execution-sdk-python>=3.0.0,<4",
133133
"opentelemetry-sdk>=1.20.0",
134134
"opentelemetry-propagator-aws-xray",
135135
"pytest",

0 commit comments

Comments
 (0)