mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 03:21:47 +08:00
fix(bundles): floor lfx pin at the minor line's .dev0 so nightlies resolve
The first 1.11.0.dev0 nightly failed its "Test Langflow Main CLI" step: the fork bump's sync_bundle_lfx_pin.py re-synced every bundle floor to lfx>=1.11.0, and PEP 440 sorts the nightly's 1.11.0.dev0 BELOW 1.11.0, so the workspace-built bundles (whose metadata shadows the satisfiable PyPI lfx-arxiv 0.1.1 during `uv pip install dist/*.whl`) reject the branch's own lfx while langflow-base pins it exactly — unresolvable. Floor at lfx>=X.Y.0.dev0,<(X+1).0.0 instead: X.Y.0.dev0 is the lowest version PEP 440 admits in the minor line, so every devN / rcN / final satisfies the floor while older lines and the next major stay excluded. This closes the NIGHTLY.md activation-gate hole structurally — future minor forks re-sync to a floor their own nightlies already satisfy. - sync_bundle_lfx_pin.py: lfx_floor_spec emits the .dev0 floor - port_bundle.py: mirrored _current_lfx_floor kept in step - bundle pyprojects restamped via the script (arxiv/docling/duckduckgo/ibm) - test_bundle_lfx_pin.py expectations updated (20/20 passing) - NIGHTLY.md gate section annotated with the post-activation fix uv.lock is unaffected (workspace lfx is recorded as an editable source with no specifier — which is also why uv sync/lock passed in the same job). The release.yml RC floor-relax sed still matches the new form; now redundant but harmless. No bundle version bump needed: published 0.1.1 floors >=1.10.0.rc0, satisfiable by the whole 1.11 line.
This commit is contained in:
@ -8,11 +8,18 @@ guaranteed to resolve an lfx new enough to carry it. Before the LFX 0.5.x ->
|
||||
no upper bound, which silently permitted resolving against the now-dead 0.5.x
|
||||
line -- and neither pip nor uv flags the cross-line jump.
|
||||
|
||||
Pin form: ``lfx>=X.Y.0,<(X+1).0.0`` -- floored at the current minor line,
|
||||
capped below the next lfx major. The cap is a coarse install-time guard
|
||||
against an untested lfx major; fine-grained BUNDLE_API compatibility is still
|
||||
enforced at load time by each ``extension.json``'s ``lfx.compat`` list against
|
||||
the running lfx's ``BUNDLE_API_VERSION`` (see
|
||||
Pin form: ``lfx>=X.Y.0.dev0,<(X+1).0.0`` -- floored at the very first
|
||||
pre-release of the current minor line, capped below the next lfx major. The
|
||||
``.dev0`` floor (not ``X.Y.0``) is load-bearing: nightlies off a release
|
||||
branch are canonical ``X.Y.0.devN`` pre-releases, and PEP 440 sorts those
|
||||
BELOW ``X.Y.0`` -- a plain ``>=X.Y.0`` floor makes the branch's own nightly
|
||||
``lfx`` unresolvable against its own bundles (langflow-base pins
|
||||
``lfx==X.Y.0.devN`` exactly, so the resolver cannot back off). ``X.Y.0.dev0``
|
||||
is the lowest version PEP 440 admits in the line, so every devN / rcN / final
|
||||
satisfies it while older minor lines stay excluded. The cap is a coarse
|
||||
install-time guard against an untested lfx major; fine-grained BUNDLE_API
|
||||
compatibility is still enforced at load time by each ``extension.json``'s
|
||||
``lfx.compat`` list against the running lfx's ``BUNDLE_API_VERSION`` (see
|
||||
``src/lfx/src/lfx/extension/manifest.py``).
|
||||
|
||||
Idempotent: re-running with the same version is a no-op (so it is safe to call
|
||||
@ -53,8 +60,10 @@ _VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.\d+")
|
||||
def lfx_floor_spec(version: str) -> str:
|
||||
"""Return the bundle ``lfx`` dependency spec for a Langflow/LFX version.
|
||||
|
||||
``"1.10.0"`` -> ``"lfx>=1.10.0,<2.0.0"`` (floor at the minor line start,
|
||||
cap below the next lfx major). A leading ``v`` is tolerated.
|
||||
``"1.11.0"`` -> ``"lfx>=1.11.0.dev0,<2.0.0"`` (floor at the minor line's
|
||||
first pre-release so the branch's own ``X.Y.0.devN`` nightlies resolve --
|
||||
see the module docstring; cap below the next lfx major). A leading ``v``
|
||||
is tolerated.
|
||||
|
||||
NOTE: this floor format is duplicated in ``scripts/migrate/port_bundle.py``
|
||||
(``_current_lfx_floor``) so each script stays standalone; keep them in step.
|
||||
@ -64,7 +73,7 @@ def lfx_floor_spec(version: str) -> str:
|
||||
msg = f"Unparseable version {version!r}; expected X.Y.Z"
|
||||
raise ValueError(msg)
|
||||
major, minor = int(match.group(1)), int(match.group(2))
|
||||
return f"lfx>={major}.{minor}.0,<{major + 1}.0.0"
|
||||
return f"lfx>={major}.{minor}.0.dev0,<{major + 1}.0.0"
|
||||
|
||||
|
||||
def rewrite_lfx_dep(content: str, floor_spec: str) -> str:
|
||||
|
||||
@ -69,11 +69,13 @@ def _current_lfx_floor() -> str:
|
||||
"""Return the ``lfx`` dependency floor for a freshly-ported bundle.
|
||||
|
||||
Reads the workspace lfx version from ``src/lfx/pyproject.toml`` and pins
|
||||
``lfx>=X.Y.0,<(X+1).0.0`` -- floored at the current major.minor line,
|
||||
capped below the next lfx major. Mirrors ``lfx_floor_spec`` in
|
||||
``scripts/ci/sync_bundle_lfx_pin.py`` (each script is kept standalone, so
|
||||
keep the two in step); that script re-syncs every existing bundle on
|
||||
``make patch``.
|
||||
``lfx>=X.Y.0.dev0,<(X+1).0.0`` -- floored at the current major.minor
|
||||
line's first pre-release (the branch's own canonical ``X.Y.0.devN``
|
||||
nightlies sort below a plain ``X.Y.0`` under PEP 440, so they must be
|
||||
admitted by the floor), capped below the next lfx major. Mirrors
|
||||
``lfx_floor_spec`` in ``scripts/ci/sync_bundle_lfx_pin.py`` (each script
|
||||
is kept standalone, so keep the two in step); that script re-syncs every
|
||||
existing bundle on ``make patch``.
|
||||
"""
|
||||
lfx_pyproject = (REPO_ROOT / "src" / "lfx" / "pyproject.toml").read_text(encoding="utf-8")
|
||||
match = re.search(r'^version = "(\d+)\.(\d+)\.\d+', lfx_pyproject, re.MULTILINE)
|
||||
@ -81,7 +83,7 @@ def _current_lfx_floor() -> str:
|
||||
msg = "Could not read lfx version from src/lfx/pyproject.toml"
|
||||
raise ValueError(msg)
|
||||
major, minor = int(match.group(1)), int(match.group(2))
|
||||
return f"lfx>={major}.{minor}.0,<{major + 1}.0.0"
|
||||
return f"lfx>={major}.{minor}.0.dev0,<{major + 1}.0.0"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -36,15 +36,18 @@ mod = _load_module()
|
||||
|
||||
|
||||
class TestLfxFloorSpec:
|
||||
# The .dev0 floor is load-bearing: nightlies are canonical X.Y.0.devN
|
||||
# pre-releases, which PEP 440 sorts BELOW X.Y.0, so a plain >=X.Y.0
|
||||
# floor makes the branch's own nightly lfx unresolvable.
|
||||
@pytest.mark.parametrize(
|
||||
("version", "expected"),
|
||||
[
|
||||
("1.10.0", "lfx>=1.10.0,<2.0.0"),
|
||||
("1.10.3", "lfx>=1.10.0,<2.0.0"), # patch within a minor -> same floor
|
||||
("1.11.0", "lfx>=1.11.0,<2.0.0"),
|
||||
("2.0.0", "lfx>=2.0.0,<3.0.0"),
|
||||
("v1.10.0", "lfx>=1.10.0,<2.0.0"), # leading v tolerated
|
||||
("10.4.2", "lfx>=10.4.0,<11.0.0"), # multi-digit major
|
||||
("1.10.0", "lfx>=1.10.0.dev0,<2.0.0"),
|
||||
("1.10.3", "lfx>=1.10.0.dev0,<2.0.0"), # patch within a minor -> same floor
|
||||
("1.11.0", "lfx>=1.11.0.dev0,<2.0.0"),
|
||||
("2.0.0", "lfx>=2.0.0.dev0,<3.0.0"),
|
||||
("v1.10.0", "lfx>=1.10.0.dev0,<2.0.0"), # leading v tolerated
|
||||
("10.4.2", "lfx>=10.4.0.dev0,<11.0.0"), # multi-digit major
|
||||
],
|
||||
)
|
||||
def test_floor(self, version, expected):
|
||||
@ -62,7 +65,7 @@ class TestLfxFloorSpec:
|
||||
|
||||
|
||||
class TestRewriteLfxDep:
|
||||
FLOOR = "lfx>=1.10.0,<2.0.0"
|
||||
FLOOR = "lfx>=1.10.0.dev0,<2.0.0"
|
||||
|
||||
def test_rewrites_bare_floor(self):
|
||||
assert mod.rewrite_lfx_dep(' "lfx>=0.5.0",', self.FLOOR) == f' "{self.FLOOR}",'
|
||||
@ -121,11 +124,11 @@ class TestSyncBundles:
|
||||
def test_sync_updates_and_reports(self, tmp_path):
|
||||
bundles = tmp_path / "bundles"
|
||||
self._make_bundle(bundles, "arxiv", "lfx>=0.5.0")
|
||||
self._make_bundle(bundles, "ibm", "lfx>=1.10.0,<2.0.0") # already correct
|
||||
self._make_bundle(bundles, "ibm", "lfx>=1.10.0.dev0,<2.0.0") # already correct
|
||||
|
||||
results = dict(mod.sync_bundles("1.10.0", bundles))
|
||||
assert results == {"arxiv": True, "ibm": False} # arxiv changed, ibm no-op
|
||||
assert '"lfx>=1.10.0,<2.0.0"' in (bundles / "arxiv" / "pyproject.toml").read_text()
|
||||
assert '"lfx>=1.10.0.dev0,<2.0.0"' in (bundles / "arxiv" / "pyproject.toml").read_text()
|
||||
|
||||
def test_sync_idempotent(self, tmp_path):
|
||||
bundles = tmp_path / "bundles"
|
||||
|
||||
@ -81,6 +81,16 @@ Why (2) is not optional: a bundle pins `lfx>=1.10.0,<2.0.0`, and PEP 440 sorts `
|
||||
daily from **main's** workflow definition, so merging this before the gate would break the live
|
||||
nightly on the next run.
|
||||
|
||||
> **Post-activation fix (2026-06):** condition (2) as stated was still fragile — the `release-1.11.0`
|
||||
> fork's `make patch v=1.11.0` re-synced every bundle floor to `lfx>=1.11.0`
|
||||
> (`scripts/ci/sync_bundle_lfx_pin.py`), which sorts **above** that same branch's `1.11.0.devN`
|
||||
> nightlies and reintroduced exactly this conflict on the very first `1.11.0.dev0` nightly (the
|
||||
> workspace-built bundle's metadata shadows the satisfiable PyPI `0.1.1` in the
|
||||
> `uv pip install dist/*.whl` test step). The synced floor format is now
|
||||
> `lfx>=X.Y.0.dev0,<(X+1).0.0` — `X.Y.0.dev0` is the lowest version PEP 440 admits in the minor
|
||||
> line, so every `devN` / `rcN` / final satisfies it while older lines stay excluded, and the gate
|
||||
> can no longer regress on future minor forks.
|
||||
|
||||
## A1 vs A2 + remaining follow-ups (decide before activating)
|
||||
|
||||
This PR implements the **A1** publish behavior: the separate `langflow-nightly` / `langflow-base-nightly`
|
||||
|
||||
@ -15,7 +15,7 @@ keywords = ["langflow", "lfx", "extension", "bundle", "arxiv", "search"]
|
||||
# but we list it here as a direct runtime dep so the bundle keeps building
|
||||
# even if lfx drops it later.
|
||||
dependencies = [
|
||||
"lfx>=1.11.0,<2.0.0",
|
||||
"lfx>=1.11.0.dev0,<2.0.0",
|
||||
"defusedxml>=0.7.1,<1.0.0",
|
||||
]
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ keywords = ["langflow", "lfx", "extension", "bundle", "docling", "documents"]
|
||||
# base dependency because the bundle exchanges DoclingDocument objects even when
|
||||
# conversion happens remotely.
|
||||
dependencies = [
|
||||
"lfx>=1.11.0,<2.0.0",
|
||||
"lfx>=1.11.0.dev0,<2.0.0",
|
||||
"httpx>=0.28.1,<1.0.0",
|
||||
"docling-core>=2.36.1,<3.0.0",
|
||||
]
|
||||
|
||||
@ -18,7 +18,7 @@ keywords = ["langflow", "lfx", "extension", "bundle", "duckduckgo", "search"]
|
||||
# "lfx": {"compat": [...]} contract. langchain-community uses the same
|
||||
# range the lfx tree does to keep co-installation lockstep.
|
||||
dependencies = [
|
||||
"lfx>=1.11.0,<2.0.0",
|
||||
"lfx>=1.11.0.dev0,<2.0.0",
|
||||
"langchain-community>=0.4.1,<1.0.0",
|
||||
"ddgs>=9.0.0",
|
||||
]
|
||||
|
||||
@ -26,7 +26,7 @@ keywords = ["langflow", "lfx", "extension", "bundle", "ibm", "db2", "watsonx", "
|
||||
# 3.10-compatible build elsewhere. Kept in lockstep with langflow-base.
|
||||
# * ``langchain-community`` provides the helpers DB2VS leans on.
|
||||
dependencies = [
|
||||
"lfx>=1.11.0,<2.0.0",
|
||||
"lfx>=1.11.0.dev0,<2.0.0",
|
||||
"langchain-community>=0.4.1,<1.0.0",
|
||||
"ibm-db>=3.2.9,<4.0.0; sys_platform != 'linux' or platform_machine != 'aarch64'",
|
||||
"ibm-watsonx-ai>=1.3.1,<2.0.0",
|
||||
|
||||
Reference in New Issue
Block a user