-
-
Notifications
You must be signed in to change notification settings - Fork 2
Edit pyproject.toml file's tool.uv.sources option if it is a uv-managed project
#81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
a4e0ab3
build: Add tomlkit as a core dependency for uv support
erral 3f94e9b
feat: Merge UvPyprojectUpdater hook directly into mxdev core
erral ba6ee86
test: Add proper tests for UvPyprojectUpdater hook with managed=true …
erral 6e3d691
docs: Document UV pyproject updater integration
erral 2670512
fix: Update entrypoint key to 'hook' and attribute contributor in CHA…
erral 5ca157c
test: Add specific test for managed=false skipping
erral b5b0637
docs: Move and expand UV Pyproject Integration documentation
erral ca05f18
docs: Remove duplicate UV Pyproject Integration section
erral c2b3360
test: Rework test_hook_skips_when_pyproject_toml_missing to use tmp_path
erral 645f5f7
Update README.md
erral bed9e2c
build: Make tomlkit optional and update documentation per PR review
erral 24c505b
refactor: Lazy load tomlkit, use atomic writes, and fix path resolution
erral 0a07ad3
refactor: Remove project.dependencies mutation and clean up dead code
erral da10744
test: Add missing test coverage and remove obsolete tests
erral 6dedfa7
move imports to the top of the file
erral 415a594
move imports to the top of the file
erral 24ef1bd
Use TYPE_CHECKING for tomlkit type hint
erral 86f69c7
fix: Defer tomlkit import until uv management is confirmed
erral 05df311
fix: Clean up temporary files on write failure
erral 49bb647
test: Remove misleading relative path resolution test
erral 2860b1b
fix: Defer tomlkit import using tomllib fallback strategy
erral File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| from mxdev.hooks import Hook | ||
| from mxdev.state import State | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| import logging | ||
| import os | ||
| import tempfile | ||
|
|
||
|
|
||
| if TYPE_CHECKING: | ||
| import tomlkit | ||
|
|
||
|
|
||
| logger = logging.getLogger("mxdev") | ||
|
|
||
|
|
||
| class UvPyprojectUpdater(Hook): | ||
| """An mxdev hook that updates pyproject.toml during the write phase for uv-managed projects.""" | ||
|
|
||
| namespace = "uv" | ||
|
|
||
| def read(self, state: State) -> None: | ||
| pass | ||
|
|
||
| def write(self, state: State) -> None: | ||
| pyproject_path = Path(state.configuration.settings.get("directory", ".")) / "pyproject.toml" | ||
| if not pyproject_path.exists(): | ||
| logger.debug("[%s] pyproject.toml not found, skipping.", self.namespace) | ||
| return | ||
|
|
||
| try: | ||
| content = pyproject_path.read_text(encoding="utf-8") | ||
| except OSError as e: | ||
| logger.error("[%s] Failed to read pyproject.toml: %s", self.namespace, e) | ||
erral marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return | ||
|
|
||
| # Attempt to parse using standard library (Python 3.11+) | ||
| try: | ||
| import tomllib | ||
|
|
||
| parsed = tomllib.loads(content) | ||
| if parsed.get("tool", {}).get("uv", {}).get("managed") is not True: | ||
| logger.debug( | ||
| "[%s] Project not explicitly managed by uv ([tool.uv] managed=true missing), skipping.", | ||
| self.namespace, | ||
| ) | ||
| return | ||
| except ImportError: | ||
| # Fallback for Python 3.10: fast string check to avoid tomlkit overhead | ||
| if "[tool.uv]" not in content: | ||
| logger.debug( | ||
| "[%s] Project not explicitly managed by uv ([tool.uv] managed=true missing), skipping.", | ||
| self.namespace, | ||
| ) | ||
| return | ||
| except Exception: | ||
| # If the parser fails (e.g., malformed TOML), just skip. | ||
| return | ||
|
|
||
| # Now we are confident it's a uv project, require our heavy dependency | ||
| try: | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| if not TYPE_CHECKING: | ||
| import tomlkit | ||
| except ImportError: | ||
| raise RuntimeError("tomlkit is required for the uv hook. Install it with: pip install mxdev[uv]") | ||
|
|
||
| doc = tomlkit.loads(content) | ||
|
|
||
| # Check for the UV managed signal | ||
| tool_uv = doc.get("tool", {}).get("uv", {}) | ||
| if tool_uv.get("managed") is not True: | ||
| logger.debug( | ||
| "[%s] Project not explicitly managed by uv ([tool.uv] managed=true missing), skipping.", self.namespace | ||
| ) | ||
| return | ||
|
|
||
| logger.info("[%s] Updating pyproject.toml...", self.namespace) | ||
| self._update_pyproject(doc, state) | ||
|
|
||
| tmp = None | ||
| try: | ||
| with tempfile.NamedTemporaryFile( | ||
| mode="w", dir=pyproject_path.parent, suffix=".tmp", delete=False, encoding="utf-8" | ||
| ) as f: | ||
| tomlkit.dump(doc, f) | ||
| tmp = f.name | ||
| os.replace(tmp, str(pyproject_path)) | ||
| tmp = None # success, don't clean up | ||
| logger.info("[%s] Successfully updated pyproject.toml", self.namespace) | ||
| except OSError as e: | ||
erral marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| logger.error("[%s] Failed to write pyproject.toml: %s", self.namespace, e) | ||
| finally: | ||
| if tmp and os.path.exists(tmp): | ||
| os.unlink(tmp) | ||
|
|
||
erral marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| def _update_pyproject(self, doc: "tomlkit.TOMLDocument", state: State) -> None: | ||
| """Modify the pyproject.toml document based on mxdev state.""" | ||
erral marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import tomlkit | ||
|
|
||
| if not state.configuration.packages: | ||
| return | ||
|
|
||
| # 1. Update [tool.uv.sources] | ||
| if "tool" not in doc: | ||
| doc.add("tool", tomlkit.table()) | ||
| if "uv" not in doc["tool"]: | ||
| doc["tool"]["uv"] = tomlkit.table() | ||
| if "sources" not in doc["tool"]["uv"]: | ||
| doc["tool"]["uv"]["sources"] = tomlkit.table() | ||
|
|
||
| uv_sources = doc["tool"]["uv"]["sources"] | ||
|
|
||
| for pkg_name, pkg_data in state.configuration.packages.items(): | ||
| install_mode = pkg_data.get("install-mode", "editable") | ||
|
|
||
| if install_mode == "skip": | ||
| continue | ||
|
|
||
| target_dir = Path(pkg_data.get("target", "sources")) | ||
| package_path = target_dir / pkg_name | ||
| subdirectory = pkg_data.get("subdirectory", "") | ||
| if subdirectory: | ||
| package_path = package_path / subdirectory | ||
|
|
||
| try: | ||
| if package_path.is_absolute(): | ||
| rel_path = package_path.relative_to(Path.cwd()).as_posix() | ||
| else: | ||
| rel_path = package_path.as_posix() | ||
| except ValueError: | ||
| rel_path = package_path.as_posix() | ||
|
|
||
erral marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| source_table = tomlkit.inline_table() | ||
| source_table.append("path", rel_path) | ||
|
|
||
| if install_mode == "editable": | ||
| source_table.append("editable", True) | ||
| elif install_mode == "fixed": | ||
| source_table.append("editable", False) | ||
|
|
||
| uv_sources[pkg_name] = source_table | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.