From fc5776846d0868cc1b610b34a9bfef88064c5021 Mon Sep 17 00:00:00 2001 From: Scott Florentino <3495150+smflorentino@users.noreply.github.com> Date: Thu, 8 Oct 2026 12:58:10 -0700 Subject: [PATCH 1/4] build: embed source git commit in built package Add a hatchling custom build hook (hatch_build.py) that generates a git-ignored _version.py at build time, following the setuptools-scm / hatch-vcs convention: __version__ and __commit_id__ (aliases version, commit_id). The file ships in both the sdist and the wheel, so an installed package can report the exact commit it was built from. The commit is HEAD of the checkout (only when the project is tracked by it); when building a wheel from an sdist the commit recorded in the sdist is kept. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 3 ++ hatch_build.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 8 +++++ 3 files changed, 100 insertions(+) create mode 100644 hatch_build.py diff --git a/.gitignore b/.gitignore index e4c6a771a..6865f9e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -203,3 +203,6 @@ testcases/**/CLAUDE.md testcases/**/.agent/ testcases/**/agent.mermaid testcases/**/project.uiproj + +# Generated at build time by hatch_build.py +/src/uipath_langchain/_version.py diff --git a/hatch_build.py b/hatch_build.py new file mode 100644 index 000000000..3436b1187 --- /dev/null +++ b/hatch_build.py @@ -0,0 +1,89 @@ +"""Hatch build hook that stamps the source git commit into the built package. + +Writes the file named by ``version-file`` in ``[tool.hatch.build.hooks.custom]`` +using the setuptools-scm / hatch-vcs convention (``__version__`` and +``__commit_id__``), so an installed package can report the exact commit it was +built from:: + + from ._version import __commit_id__ + +The file is generated at build time and git-ignored. The commit comes from +``git rev-parse HEAD`` when building from a checkout of this repo. When there is +no checkout (e.g. building a wheel from an sdist), the commit already recorded +in the file is kept. If neither is available, ``__commit_id__`` is ``None``. +""" + +import ast +import subprocess +from pathlib import Path +from typing import Any + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + +TEMPLATE = '''\ +"""Version and source commit of this build. Generated by hatch_build.py.""" + +__all__ = ["__version__", "__commit_id__", "version", "commit_id"] + +__version__ = version = {version!r} +__commit_id__ = commit_id = {commit_id!r} +''' + + +class CommitIdBuildHook(BuildHookInterface): + """Generate the version file before each build.""" + + PLUGIN_NAME = "custom" + + def initialize(self, version: str, build_data: dict[str, Any]) -> None: + """Write the version file and ship it despite it being git-ignored.""" + relative_path = self.config["version-file"] + version_file = Path(self.root) / relative_path + commit_id = _git_commit(Path(self.root)) or _recorded_commit(version_file) + version_file.write_text( + TEMPLATE.format(version=self.metadata.version, commit_id=commit_id), + encoding="utf-8", + ) + build_data["artifacts"].append(f"/{relative_path}") + + +def _git_commit(root: Path) -> str | None: + """Return HEAD's full SHA if ``root`` is tracked in a git checkout.""" + try: + # Confirms root belongs to this repo, not to an unrelated parent checkout. + subprocess.run( + ["git", "ls-files", "--error-unmatch", "pyproject.toml"], + cwd=root, + check=True, + capture_output=True, + ) + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + cwd=root, + check=True, + capture_output=True, + text=True, + ) + except (OSError, subprocess.CalledProcessError): + return None + return result.stdout.strip() or None + + +def _recorded_commit(version_file: Path) -> str | None: + """Return the commit already in ``version_file`` (e.g. one shipped in an sdist).""" + try: + tree = ast.parse(version_file.read_text(encoding="utf-8")) + except (OSError, SyntaxError): + return None + for node in tree.body: + if ( + isinstance(node, ast.Assign) + and any( + isinstance(t, ast.Name) and t.id == "__commit_id__" + for t in node.targets + ) + and isinstance(node.value, ast.Constant) + and isinstance(node.value.value, str) + ): + return node.value.value + return None diff --git a/pyproject.toml b/pyproject.toml index 4632160bf..f5ec45b4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,6 +104,9 @@ dev = [ "uvicorn>=0.30.0", ] +[tool.hatch.build.hooks.custom] +version-file = "src/uipath_langchain/_version.py" + [tool.hatch.build.targets.wheel] packages = ["src/uipath_langchain"] @@ -140,6 +143,11 @@ no_implicit_reexport = true disallow_untyped_defs = false +[[tool.mypy.overrides]] +# hatchling is only installed in the isolated build env (used by hatch_build.py). +module = ["hatchling.*"] +ignore_missing_imports = true + [tool.pytest.ini_options] testpaths = ["tests"] python_files = "test_*.py" From 1fc626e9cd23a085f227e03e5857c45243a869d2 Mon Sep 17 00:00:00 2001 From: Scott Florentino <3495150+smflorentino@users.noreply.github.com> Date: Thu, 8 Oct 2026 13:38:21 -0700 Subject: [PATCH 2/4] build: type-check hatch_build.py against real hatchling Add hatchling as a dev dependency instead of exempting it from mypy, and bound the build backend to the same range (hatchling>=1.32,<2) so the version mypy checks against matches the one that runs the hook. Give BuildHookInterface its BuilderConfig type argument, which mypy now requires. uv.lock regenerated with uv 0.12, which also bumps the lockfile format revision (3 -> 5); no existing package versions change. Co-Authored-By: Claude Opus 5.5 --- hatch_build.py | 3 ++- pyproject.toml | 8 ++---- uv.lock | 72 +++++++++++++++++++++++++++++++++++++------------- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/hatch_build.py b/hatch_build.py index 3436b1187..07699b728 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -18,6 +18,7 @@ from pathlib import Path from typing import Any +from hatchling.builders.config import BuilderConfig from hatchling.builders.hooks.plugin.interface import BuildHookInterface TEMPLATE = '''\ @@ -30,7 +31,7 @@ ''' -class CommitIdBuildHook(BuildHookInterface): +class CommitIdBuildHook(BuildHookInterface[BuilderConfig]): """Generate the version file before each build.""" PLUGIN_NAME = "custom" diff --git a/pyproject.toml b/pyproject.toml index f5ec45b4e..ef6fdcf64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ Repository = "https://github.com/UiPath/uipath-langchain-python" Documentation = "https://uipath.github.io/uipath-python/" [build-system] -requires = ["hatchling"] +requires = ["hatchling>=1.32,<2"] build-backend = "hatchling.build" [dependency-groups] @@ -102,6 +102,7 @@ dev = [ # directly, so they are declared here rather than relied on by accident. "starlette>=1.3.1, <2.0.0", "uvicorn>=0.30.0", + "hatchling>=1.32,<2", ] [tool.hatch.build.hooks.custom] @@ -143,11 +144,6 @@ no_implicit_reexport = true disallow_untyped_defs = false -[[tool.mypy.overrides]] -# hatchling is only installed in the isolated build env (used by hatch_build.py). -module = ["hatchling.*"] -ignore_missing_imports = true - [tool.pytest.ini_options] testpaths = ["tests"] python_files = "test_*.py" diff --git a/uv.lock b/uv.lock index 8748475f8..9f8a6357d 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.15'", @@ -14,13 +14,13 @@ exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for exclude-newer-span = "P2D" [options.exclude-newer-package] +jsonschema-pydantic-converter = false uipath = false +uipath-core = false +uipath-langchain-client = false +uipath-llm-client = false uipath-platform = false uipath-runtime = false -uipath-llm-client = false -jsonschema-pydantic-converter = false -uipath-langchain-client = false -uipath-core = false [[package]] name = "a2a-sdk" @@ -173,9 +173,9 @@ name = "aiologic" version = "0.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sniffio", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, - { name = "wrapt", marker = "python_full_version < '3.13'" }, + { name = "sniffio" }, + { name = "typing-extensions" }, + { name = "wrapt" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/7a/d51f2fde1e8ae8a83431f8e97b7a71e9358cdb1d4d2ce6be387fa44d68de/aiologic-0.17.1.tar.gz", hash = "sha256:2e1b93b9e88ced318c2a63ad7b382688f40cbfe40e3d42258d49dc9c5aea179d", size = 252354, upload-time = "2026-06-27T20:41:33.25Z" } wheels = [ @@ -456,12 +456,12 @@ name = "black" version = "26.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, - { name = "mypy-extensions", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, - { name = "packaging", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, - { name = "pathspec", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, - { name = "platformdirs", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, - { name = "pytokens", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } wheels = [ @@ -1042,8 +1042,8 @@ name = "culsans" version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiologic", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "aiologic" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" } wheels = [ @@ -1648,6 +1648,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "hatchling" +version = "1.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, + { name = "pluggy" }, + { name = "tomlkit" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/97/b5312f01a8c6daf729a9d272dd442e0c546dbcc630495788786c4b567ed0/hatchling-1.32.4.tar.gz", hash = "sha256:c4468f73144c054d2aab4ef0f0378c43b9878bf07f8ffd6b79690e970d375f07", size = 58065, upload-time = "2026-09-20T22:48:45.398Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/80/91f51f439c05d4ec4623c22928ce16a938d6d793bf709477830823497859/hatchling-1.32.4-py3-none-any.whl", hash = "sha256:08ecf7548fb48205e7f213d70c71e67b8271b7242093dc3f1da578b42c734a2c", size = 78748, upload-time = "2026-09-20T22:48:44.19Z" }, +] + [[package]] name = "hf-xet" version = "1.5.1" @@ -1698,8 +1714,8 @@ name = "httpcore2" version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "h11", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, - { name = "truststore", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, + { name = "h11" }, + { name = "truststore" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" } wheels = [ @@ -4663,6 +4679,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] +[[package]] +name = "tomlkit" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/96/e07752635b98536177fa1f37671c8f3cdde2e724c6bcf6034b2cfb571565/tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97", size = 180129, upload-time = "2026-07-17T01:48:04.562Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/bc/8c13eb66537dce1d2bd3a57132902f38d0e7f5bb46fa9f4daed9fe9d76ee/tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304", size = 49449, upload-time = "2026-07-17T01:48:05.728Z" }, +] + [[package]] name = "tqdm" version = "4.68.3" @@ -4675,6 +4700,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, ] +[[package]] +name = "trove-classifiers" +version = "2026.9.21.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/93/af436dfaa845cab5d96f0adbc1e4f3730532d37fa249e4eb796fb1d7fc82/trove_classifiers-2026.9.21.13.tar.gz", hash = "sha256:0a9ebc8d4e2f3e8a22848c5258033035bec17a3012ac3fea16dbaa764489eb71", size = 17085, upload-time = "2026-09-21T13:29:07.301Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/81/0da8afb52a71d0a4f2bd3152357b1a441e393b286374802b9d3addab4ab5/trove_classifiers-2026.9.21.13-py3-none-any.whl", hash = "sha256:8b1ff4f9c191b1040b71c37f1e445ab99732911e3cd91de52838453a854d7a17", size = 14232, upload-time = "2026-09-21T13:29:06.123Z" }, +] + [[package]] name = "truststore" version = "0.10.4" @@ -4882,6 +4916,7 @@ vertex = [ [package.dev-dependencies] dev = [ { name = "filelock" }, + { name = "hatchling" }, { name = "mypy" }, { name = "numpy" }, { name = "packaging" }, @@ -4940,6 +4975,7 @@ provides-extras = ["anthropic", "vertex", "bedrock", "fireworks", "code-interpre [package.metadata.requires-dev] dev = [ { name = "filelock", specifier = ">=3.20.3" }, + { name = "hatchling", specifier = ">=1.32,<2" }, { name = "mypy", specifier = ">=1.14.1" }, { name = "numpy", specifier = ">=1.24.0" }, { name = "packaging", specifier = ">=24.0" }, From 2f15b3e0715e4a6f9090fa34b28829e88da7f113 Mon Sep 17 00:00:00 2001 From: Scott Florentino <3495150+smflorentino@users.noreply.github.com> Date: Thu, 8 Oct 2026 13:40:23 -0700 Subject: [PATCH 3/4] build: drop hatchling version bounds The hook works on any recent hatchling (checked 1.18 through 1.32), isolated builds always fetch the latest, and uv.lock already pins the dev version, so the bounds added nothing. build-system requires is back to its original form. Co-Authored-By: Claude Opus 5.5 --- pyproject.toml | 4 ++-- uv.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ef6fdcf64..e29bd7132 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ Repository = "https://github.com/UiPath/uipath-langchain-python" Documentation = "https://uipath.github.io/uipath-python/" [build-system] -requires = ["hatchling>=1.32,<2"] +requires = ["hatchling"] build-backend = "hatchling.build" [dependency-groups] @@ -102,7 +102,7 @@ dev = [ # directly, so they are declared here rather than relied on by accident. "starlette>=1.3.1, <2.0.0", "uvicorn>=0.30.0", - "hatchling>=1.32,<2", + "hatchling", ] [tool.hatch.build.hooks.custom] diff --git a/uv.lock b/uv.lock index 9f8a6357d..47d2d50c8 100644 --- a/uv.lock +++ b/uv.lock @@ -4975,7 +4975,7 @@ provides-extras = ["anthropic", "vertex", "bedrock", "fireworks", "code-interpre [package.metadata.requires-dev] dev = [ { name = "filelock", specifier = ">=3.20.3" }, - { name = "hatchling", specifier = ">=1.32,<2" }, + { name = "hatchling" }, { name = "mypy", specifier = ">=1.14.1" }, { name = "numpy", specifier = ">=1.24.0" }, { name = "packaging", specifier = ">=24.0" }, From 501dc7cdf817ce92b4e53fac5b7ac963b5044457 Mon Sep 17 00:00:00 2001 From: Scott Florentino <3495150+smflorentino@users.noreply.github.com> Date: Thu, 8 Oct 2026 13:41:43 -0700 Subject: [PATCH 4/4] chore: bump version so the next release carries the commit id Co-Authored-By: Claude Opus 5.5 --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e29bd7132..d344a4943 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-langchain" -version = "0.18.30" +version = "0.18.31" description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/uv.lock b/uv.lock index 47d2d50c8..5a1a522a9 100644 --- a/uv.lock +++ b/uv.lock @@ -4862,7 +4862,7 @@ wheels = [ [[package]] name = "uipath-langchain" -version = "0.18.30" +version = "0.18.31" source = { editable = "." } dependencies = [ { name = "a2a-sdk" },