Skip to content

Commit 640dd28

Browse files
Python: Add RPC codecs for PythonResolutionResult marker (#6832)
When PythonRewriteRpc.parseProject() adds a PythonResolutionResult marker to setup.py on the Java side, the subsequent printAll() sends this modified tree back to Python via the RPC diff protocol. Without Python-side codecs, the marker couldn't be deserialized, causing a message stream desync ("Expected positions array" error). Add Python dataclasses and bidirectional RPC codecs (send + receive) for PythonResolutionResult and its nested types: Dependency, ResolvedDependency, SourceIndex, and PackageManager.
1 parent 19c94ab commit 640dd28

3 files changed

Lines changed: 667 additions & 1 deletion

File tree

rewrite-python/rewrite/src/rewrite/python/markers.py

Lines changed: 256 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass, replace
4-
from enum import Enum
4+
from enum import Enum, auto
5+
from typing import Optional, List, Dict
56
from uuid import UUID
67

78
from rewrite import Marker
@@ -139,3 +140,257 @@ def id(self) -> UUID:
139140

140141
def with_id(self, id_: UUID) -> 'ExecSyntax':
141142
return self if id_ is self._id else replace(self, _id=id_)
143+
144+
145+
@dataclass(frozen=True, eq=False)
146+
class PythonResolutionResult(Marker):
147+
"""Contains metadata about a Python project, parsed from pyproject.toml and uv.lock."""
148+
149+
class PackageManager(Enum):
150+
Uv = auto()
151+
Pip = auto()
152+
Pipenv = auto()
153+
Poetry = auto()
154+
Pdm = auto()
155+
156+
@dataclass(frozen=True, eq=False)
157+
class SourceIndex:
158+
_name: str
159+
_url: str
160+
_default_index: bool
161+
162+
@property
163+
def name(self) -> str:
164+
return self._name
165+
166+
def with_name(self, name: str) -> PythonResolutionResult.SourceIndex:
167+
return self if name is self._name else replace(self, _name=name)
168+
169+
@property
170+
def url(self) -> str:
171+
return self._url
172+
173+
def with_url(self, url: str) -> PythonResolutionResult.SourceIndex:
174+
return self if url is self._url else replace(self, _url=url)
175+
176+
@property
177+
def default_index(self) -> bool:
178+
return self._default_index
179+
180+
def with_default_index(self, default_index: bool) -> PythonResolutionResult.SourceIndex:
181+
return self if default_index is self._default_index else replace(self, _default_index=default_index)
182+
183+
@dataclass(frozen=True, eq=False)
184+
class ResolvedDependency:
185+
_name: str
186+
_version: str
187+
_source: Optional[str]
188+
_dependencies: Optional[List[PythonResolutionResult.ResolvedDependency]]
189+
190+
@property
191+
def name(self) -> str:
192+
return self._name
193+
194+
def with_name(self, name: str) -> PythonResolutionResult.ResolvedDependency:
195+
return self if name is self._name else replace(self, _name=name)
196+
197+
@property
198+
def version(self) -> str:
199+
return self._version
200+
201+
def with_version(self, version: str) -> PythonResolutionResult.ResolvedDependency:
202+
return self if version is self._version else replace(self, _version=version)
203+
204+
@property
205+
def source(self) -> Optional[str]:
206+
return self._source
207+
208+
def with_source(self, source: Optional[str]) -> PythonResolutionResult.ResolvedDependency:
209+
return self if source is self._source else replace(self, _source=source)
210+
211+
@property
212+
def dependencies(self) -> Optional[List[PythonResolutionResult.ResolvedDependency]]:
213+
return self._dependencies
214+
215+
def with_dependencies(self, dependencies: Optional[List[PythonResolutionResult.ResolvedDependency]]) -> PythonResolutionResult.ResolvedDependency:
216+
return self if dependencies is self._dependencies else replace(self, _dependencies=dependencies)
217+
218+
@dataclass(frozen=True, eq=False)
219+
class Dependency:
220+
_name: str
221+
_version_constraint: Optional[str]
222+
_extras: Optional[List[str]]
223+
_marker: Optional[str]
224+
_resolved: Optional[PythonResolutionResult.ResolvedDependency]
225+
226+
@property
227+
def name(self) -> str:
228+
return self._name
229+
230+
def with_name(self, name: str) -> PythonResolutionResult.Dependency:
231+
return self if name is self._name else replace(self, _name=name)
232+
233+
@property
234+
def version_constraint(self) -> Optional[str]:
235+
return self._version_constraint
236+
237+
def with_version_constraint(self, version_constraint: Optional[str]) -> PythonResolutionResult.Dependency:
238+
return self if version_constraint is self._version_constraint else replace(self, _version_constraint=version_constraint)
239+
240+
@property
241+
def extras(self) -> Optional[List[str]]:
242+
return self._extras
243+
244+
def with_extras(self, extras: Optional[List[str]]) -> PythonResolutionResult.Dependency:
245+
return self if extras is self._extras else replace(self, _extras=extras)
246+
247+
@property
248+
def marker(self) -> Optional[str]:
249+
return self._marker
250+
251+
def with_marker(self, marker: Optional[str]) -> PythonResolutionResult.Dependency:
252+
return self if marker is self._marker else replace(self, _marker=marker)
253+
254+
@property
255+
def resolved(self) -> Optional[PythonResolutionResult.ResolvedDependency]:
256+
return self._resolved
257+
258+
def with_resolved(self, resolved: Optional[PythonResolutionResult.ResolvedDependency]) -> PythonResolutionResult.Dependency:
259+
return self if resolved is self._resolved else replace(self, _resolved=resolved)
260+
261+
_id: UUID
262+
_name: Optional[str]
263+
_version: Optional[str]
264+
_description: Optional[str]
265+
_license: Optional[str]
266+
_path: str
267+
_requires_python: Optional[str]
268+
_build_backend: Optional[str]
269+
_build_requires: List[Dependency]
270+
_dependencies: List[Dependency]
271+
_optional_dependencies: Dict[str, List]
272+
_dependency_groups: Dict[str, List]
273+
_constraint_dependencies: List[Dependency]
274+
_override_dependencies: List[Dependency]
275+
_resolved_dependencies: List[ResolvedDependency]
276+
_package_manager: Optional[PackageManager]
277+
_source_indexes: Optional[List[SourceIndex]]
278+
279+
@property
280+
def id(self) -> UUID:
281+
return self._id
282+
283+
def with_id(self, id_: UUID) -> PythonResolutionResult:
284+
return self if id_ is self._id else replace(self, _id=id_)
285+
286+
@property
287+
def name(self) -> Optional[str]:
288+
return self._name
289+
290+
def with_name(self, name: Optional[str]) -> PythonResolutionResult:
291+
return self if name is self._name else replace(self, _name=name)
292+
293+
@property
294+
def version(self) -> Optional[str]:
295+
return self._version
296+
297+
def with_version(self, version: Optional[str]) -> PythonResolutionResult:
298+
return self if version is self._version else replace(self, _version=version)
299+
300+
@property
301+
def description(self) -> Optional[str]:
302+
return self._description
303+
304+
def with_description(self, description: Optional[str]) -> PythonResolutionResult:
305+
return self if description is self._description else replace(self, _description=description)
306+
307+
@property
308+
def license(self) -> Optional[str]:
309+
return self._license
310+
311+
def with_license(self, license: Optional[str]) -> PythonResolutionResult:
312+
return self if license is self._license else replace(self, _license=license)
313+
314+
@property
315+
def path(self) -> str:
316+
return self._path
317+
318+
def with_path(self, path: str) -> PythonResolutionResult:
319+
return self if path is self._path else replace(self, _path=path)
320+
321+
@property
322+
def requires_python(self) -> Optional[str]:
323+
return self._requires_python
324+
325+
def with_requires_python(self, requires_python: Optional[str]) -> PythonResolutionResult:
326+
return self if requires_python is self._requires_python else replace(self, _requires_python=requires_python)
327+
328+
@property
329+
def build_backend(self) -> Optional[str]:
330+
return self._build_backend
331+
332+
def with_build_backend(self, build_backend: Optional[str]) -> PythonResolutionResult:
333+
return self if build_backend is self._build_backend else replace(self, _build_backend=build_backend)
334+
335+
@property
336+
def build_requires(self) -> List[Dependency]:
337+
return self._build_requires
338+
339+
def with_build_requires(self, build_requires: List[Dependency]) -> PythonResolutionResult:
340+
return self if build_requires is self._build_requires else replace(self, _build_requires=build_requires)
341+
342+
@property
343+
def dependencies(self) -> List[Dependency]:
344+
return self._dependencies
345+
346+
def with_dependencies(self, dependencies: List[Dependency]) -> PythonResolutionResult:
347+
return self if dependencies is self._dependencies else replace(self, _dependencies=dependencies)
348+
349+
@property
350+
def optional_dependencies(self) -> Dict[str, List]:
351+
return self._optional_dependencies
352+
353+
def with_optional_dependencies(self, optional_dependencies: Dict[str, List]) -> PythonResolutionResult:
354+
return self if optional_dependencies is self._optional_dependencies else replace(self, _optional_dependencies=optional_dependencies)
355+
356+
@property
357+
def dependency_groups(self) -> Dict[str, List]:
358+
return self._dependency_groups
359+
360+
def with_dependency_groups(self, dependency_groups: Dict[str, List]) -> PythonResolutionResult:
361+
return self if dependency_groups is self._dependency_groups else replace(self, _dependency_groups=dependency_groups)
362+
363+
@property
364+
def constraint_dependencies(self) -> List[Dependency]:
365+
return self._constraint_dependencies
366+
367+
def with_constraint_dependencies(self, constraint_dependencies: List[Dependency]) -> PythonResolutionResult:
368+
return self if constraint_dependencies is self._constraint_dependencies else replace(self, _constraint_dependencies=constraint_dependencies)
369+
370+
@property
371+
def override_dependencies(self) -> List[Dependency]:
372+
return self._override_dependencies
373+
374+
def with_override_dependencies(self, override_dependencies: List[Dependency]) -> PythonResolutionResult:
375+
return self if override_dependencies is self._override_dependencies else replace(self, _override_dependencies=override_dependencies)
376+
377+
@property
378+
def resolved_dependencies(self) -> List[ResolvedDependency]:
379+
return self._resolved_dependencies
380+
381+
def with_resolved_dependencies(self, resolved_dependencies: List[ResolvedDependency]) -> PythonResolutionResult:
382+
return self if resolved_dependencies is self._resolved_dependencies else replace(self, _resolved_dependencies=resolved_dependencies)
383+
384+
@property
385+
def package_manager(self) -> Optional[PackageManager]:
386+
return self._package_manager
387+
388+
def with_package_manager(self, package_manager: Optional[PackageManager]) -> PythonResolutionResult:
389+
return self if package_manager is self._package_manager else replace(self, _package_manager=package_manager)
390+
391+
@property
392+
def source_indexes(self) -> Optional[List[SourceIndex]]:
393+
return self._source_indexes
394+
395+
def with_source_indexes(self, source_indexes: Optional[List[SourceIndex]]) -> PythonResolutionResult:
396+
return self if source_indexes is self._source_indexes else replace(self, _source_indexes=source_indexes)

0 commit comments

Comments
 (0)