Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/uipath/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath"
version = "2.14.9"
version = "2.14.10"
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
36 changes: 7 additions & 29 deletions packages/uipath/src/uipath/_cli/cli_new.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import json
import os
import re
import shutil
import uuid

Expand All @@ -15,33 +14,11 @@

console = ConsoleLogger()

FALLBACK_UIPATH_MINOR = "2.14"


def _minor_range_spec(major: int, minor: int) -> str:
return f"uipath>={major}.{minor}.0, <{major}.{minor + 1}.0"


def _fallback_uipath_dependency_spec() -> str:
major, minor = (int(part) for part in FALLBACK_UIPATH_MINOR.split("."))
return _minor_range_spec(major, minor)


def _uipath_dependency_spec() -> str:
from . import _get_safe_version

installed = _get_safe_version()
# Strip pre-release/dev/local suffixes: only the leading "major.minor" matters.
match = re.match(r"^(\d+)\.(\d+)", installed)
if match is None:
fallback = _fallback_uipath_dependency_spec()
console.warning(
f"Could not determine the installed 'uipath' version ('{installed}'); "
f"falling back to '{fallback}'. Pin it manually if needed."
)
return fallback

return _minor_range_spec(int(match.group(1)), int(match.group(2)))
# The `uipath` minor release that scaffolded projects are pinned to.
# Deliberately a constant: the guard test in tests/cli/test_new.py fails on
# every minor bump so the scaffold (pin, template, hints) gets reviewed
# alongside the release rather than drifting silently.
UIPATH_SCAFFOLD_MINOR = "2.14"


def generate_script(target_directory):
Expand All @@ -55,13 +32,14 @@ def generate_script(target_directory):

def generate_pyproject(target_directory, project_name):
project_toml_path = os.path.join(target_directory, PYTHON_CONFIGURATION_FILE)
major, minor = (int(part) for part in UIPATH_SCAFFOLD_MINOR.split("."))
Comment thread
andreibalas-uipath marked this conversation as resolved.
toml_content = f"""[project]
name = "{project_name}"
version = "0.0.1"
description = "{project_name}"
authors = [{{ name = "John Doe", email = "john.doe@myemail.com" }}]
dependencies = [
"{_uipath_dependency_spec()}"
"uipath>={major}.{minor}.0, <{major}.{minor + 1}.0"
]
requires-python = ">=3.11"
"""
Expand Down
64 changes: 19 additions & 45 deletions packages/uipath/tests/cli/test_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from click.testing import CliRunner
from packaging.specifiers import SpecifierSet
from packaging.version import Version

from uipath._cli import cli
from uipath._cli.middlewares import MiddlewareResult
Expand Down Expand Up @@ -98,60 +99,33 @@ def test_new_project_error_handling(self, runner: CliRunner, temp_dir: str) -> N
assert "Created 'main.py' file." not in result.output


class TestUipathDependencySpec:
"""The scaffolded pin must follow the installed uipath version."""

def _written_pin(self, temp_dir: str) -> str:
from uipath._cli.cli_new import generate_pyproject

generate_pyproject(temp_dir, "demo")
with open(os.path.join(temp_dir, "pyproject.toml")) as f:
content = f.read()
match = re.search(r'"(uipath[^"]*)"', content)
assert match is not None, content
return match.group(1)

def test_pin_derived_from_installed_version(self, temp_dir: str) -> None:
with patch("uipath._cli._get_safe_version", return_value="2.14.7"):
assert self._written_pin(temp_dir) == "uipath>=2.14.0, <2.15.0"

def test_pin_strips_prerelease_suffix(self, temp_dir: str) -> None:
with patch("uipath._cli._get_safe_version", return_value="2.15.0rc1"):
assert self._written_pin(temp_dir) == "uipath>=2.15.0, <2.16.0"

def test_pin_falls_back_when_version_unknown(self, temp_dir: str) -> None:
from uipath._cli.cli_new import _fallback_uipath_dependency_spec, console

# _get_safe_version() returns "unknown" on PackageNotFoundError.
with (
patch("uipath._cli._get_safe_version", return_value="unknown"),
patch.object(console, "warning") as mock_warning,
):
assert self._written_pin(temp_dir) == _fallback_uipath_dependency_spec()
mock_warning.assert_called_once()
assert (
"Could not determine the installed 'uipath' version"
in (mock_warning.call_args.args[0])
)
class TestUipathScaffoldPin:
"""The scaffolded pin must admit the uipath release it ships with."""

def test_fallback_pin_admits_installed_uipath(self) -> None:
"""Guard: a release PR that forgets to bump FALLBACK_UIPATH_MINOR fails CI."""
from uipath._cli.cli_new import _fallback_uipath_dependency_spec
def test_scaffold_pin_admits_installed_uipath(self) -> None:
"""Guard: fails on every minor bump so the scaffold gets reviewed.

spec = _fallback_uipath_dependency_spec().removeprefix("uipath")
installed = version("uipath")
assert SpecifierSet(spec).contains(installed, prereleases=True), (
f"fallback pin '{spec}' does not admit installed uipath {installed}; "
"bump FALLBACK_UIPATH_MINOR in cli_new.py"
When this fails, review the scaffold in ``cli_new.py`` (pin constant,
``main.py`` template, post-scaffold hints) for the new minor, then bump
``UIPATH_SCAFFOLD_MINOR``.
"""
from uipath._cli.cli_new import UIPATH_SCAFFOLD_MINOR

installed = Version(version("uipath"))
installed_minor = f"{installed.major}.{installed.minor}"
assert UIPATH_SCAFFOLD_MINOR == installed_minor, (
f"uipath minor changed to {installed_minor} but UIPATH_SCAFFOLD_MINOR is "
f"{UIPATH_SCAFFOLD_MINOR}; review the scaffold in cli_new.py (pin, "
f"template, hints) and bump the constant"
)

def test_scaffolded_pin_contains_installed_uipath(
self, runner: CliRunner, temp_dir: str
) -> None:
"""Regression guard: runs against the real installed package, not a mock.

A stale hard-coded range would make ``uv sync`` downgrade the project's
venv right after ``uipath new``.
A stale range would make ``uv sync`` downgrade the project's venv right
after ``uipath new``.
"""
with runner.isolated_filesystem(temp_dir=temp_dir):
result = runner.invoke(cli, ["new", "demo"])
Expand Down
2 changes: 1 addition & 1 deletion packages/uipath/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading