1717)
1818
1919from pip ._vendor import tomli_w
20- from pip ._vendor .packaging .markers import InvalidMarker , Marker
21- from pip ._vendor .packaging .specifiers import InvalidSpecifier , SpecifierSet
22- from pip ._vendor .packaging .version import InvalidVersion , Version
20+ from pip ._vendor .packaging .markers import Marker
21+ from pip ._vendor .packaging .specifiers import SpecifierSet
22+ from pip ._vendor .packaging .version import Version
2323from pip ._vendor .typing_extensions import Self
2424
2525from pip ._internal .models .direct_url import ArchiveInfo , DirInfo , VcsInfo
2828from pip ._internal .utils .urls import url_to_path
2929
3030T = TypeVar ("T" )
31+ T2 = TypeVar ("T2" )
3132
3233
33- class PylockDataClass (Protocol ):
34+ class FromDictProtocol (Protocol ):
3435 @classmethod
3536 def from_dict (cls , d : Dict [str , Any ]) -> Self :
3637 pass
3738
3839
39- PylockDataClassT = TypeVar ("PylockDataClassT " , bound = PylockDataClass )
40+ FromDictProtocolT = TypeVar ("FromDictProtocolT " , bound = FromDictProtocol )
4041
4142PYLOCK_FILE_NAME_RE = re .compile (r"^pylock\.([^.]+)\.toml$" )
4243
@@ -67,12 +68,12 @@ def _toml_dict_factory(data: List[Tuple[str, Any]]) -> Dict[str, Any]:
6768
6869def _get (d : Dict [str , Any ], expected_type : Type [T ], key : str ) -> Optional [T ]:
6970 """Get value from dictionary and verify expected type."""
70- if key not in d :
71+ value = d .get (key )
72+ if value is None :
7173 return None
72- value = d [key ]
7374 if not isinstance (value , expected_type ):
7475 raise PylockValidationError (
75- f"{ value !r } has unexpected type for { key } (expected { expected_type } )"
76+ f"{ key } has unexpected type { type ( value ) } (expected { expected_type } )"
7677 )
7778 return value
7879
@@ -85,99 +86,94 @@ def _get_required(d: Dict[str, Any], expected_type: Type[T], key: str) -> T:
8586 return value
8687
8788
88- def _get_version (d : Dict [str , Any ], key : str ) -> Optional [Version ]:
89- value = _get (d , str , key )
89+ def _get_as (
90+ d : Dict [str , Any ], expected_type : Type [T ], target_type : Type [T2 ], key : str
91+ ) -> Optional [T2 ]:
92+ """Get value from dictionary, verify expected type, convert to target type.
93+
94+ This assumes the target_type constructor accepts the value.
95+ """
96+ value = _get (d , expected_type , key )
9097 if value is None :
9198 return None
9299 try :
93- return Version (value )
94- except InvalidVersion :
95- raise PylockUnsupportedVersionError (f"invalid version { value !r} " )
100+ return target_type (value ) # type: ignore[call-arg]
101+ except Exception as e :
102+ raise PylockValidationError (f"Error parsing value of { key !r} : { e } " ) from e
96103
97104
98- def _get_required_version (d : Dict [str , Any ], key : str ) -> Version :
99- value = _get_version (d , key )
105+ def _get_required_as (
106+ d : Dict [str , Any ], expected_type : Type [T ], target_type : Type [T2 ], key : str
107+ ) -> T2 :
108+ """Get required value from dictionary, verify expected type,
109+ convert to target type."""
110+ value = _get_as (d , expected_type , target_type , key )
100111 if value is None :
101112 raise PylockRequiredKeyError (key )
102113 return value
103114
104115
105- def _get_marker (d : Dict [str , Any ], key : str ) -> Optional [Marker ]:
106- value = _get (d , str , key )
107- if value is None :
108- return None
109- try :
110- return Marker (value )
111- except InvalidMarker :
112- raise PylockValidationError (f"invalid marker { value !r} " )
113-
114-
115- def _get_list_of_markers (d : Dict [str , Any ], key : str ) -> Optional [List [Marker ]]:
116+ def _get_list_as (
117+ d : Dict [str , Any ], expected_type : Type [T ], target_type : Type [T2 ], key : str
118+ ) -> Optional [List [T2 ]]:
116119 """Get list value from dictionary and verify expected items type."""
117- if key not in d :
120+ value = _get (d , list , key )
121+ if value is None :
118122 return None
119- value = d [key ]
120- if not isinstance (value , list ):
121- raise PylockValidationError (f"{ key !r} is not a list" )
122123 result = []
123124 for i , item in enumerate (value ):
124- if not isinstance (item , str ):
125- raise PylockValidationError (f"Item { i } in list { key !r} is not a string" )
126- try :
127- result .append (Marker (item ))
128- except InvalidMarker :
125+ if not isinstance (item , expected_type ):
129126 raise PylockValidationError (
130- f"Item { i } in list { key !r} is not a valid environment marker: { item !r} "
127+ f"Item { i } of { key } has unpexpected type { type (item )} "
128+ f"(expected { expected_type } )"
131129 )
130+ try :
131+ result .append (target_type (item )) # type: ignore[call-arg]
132+ except Exception as e :
133+ raise PylockValidationError (
134+ f"Error parsing item { i } of { key !r} : { e } "
135+ ) from e
132136 return result
133137
134138
135- def _get_specifier_set (d : Dict [str , Any ], key : str ) -> Optional [SpecifierSet ]:
136- value = _get (d , str , key )
137- if value is None :
138- return None
139- try :
140- return SpecifierSet (value )
141- except InvalidSpecifier :
142- raise PylockValidationError (f"invalid version specifier { value !r} " )
143-
144-
145139def _get_object (
146- d : Dict [str , Any ], expected_type : Type [PylockDataClassT ], key : str
147- ) -> Optional [PylockDataClassT ]:
140+ d : Dict [str , Any ], target_type : Type [FromDictProtocolT ], key : str
141+ ) -> Optional [FromDictProtocolT ]:
148142 """Get dictionary value from dictionary and convert to dataclass."""
149- if key not in d :
143+ value = _get (d , dict , key )
144+ if value is None :
150145 return None
151- value = d [ key ]
152- if not isinstance (value , dict ):
153- raise PylockValidationError ( f" { key !r } is not a dictionary" )
154- return expected_type . from_dict ( value )
146+ try :
147+ return target_type . from_dict (value )
148+ except Exception as e :
149+ raise PylockValidationError ( f"Error parsing value of { key !r } : { e } " ) from e
155150
156151
157152def _get_list_of_objects (
158- d : Dict [str , Any ], expected_type : Type [PylockDataClassT ], key : str
159- ) -> Optional [List [PylockDataClassT ]]:
153+ d : Dict [str , Any ], target_type : Type [FromDictProtocolT ], key : str
154+ ) -> Optional [List [FromDictProtocolT ]]:
160155 """Get list value from dictionary and convert items to dataclass."""
161- if key not in d :
156+ value = _get (d , list , key )
157+ if value is None :
162158 return None
163- value = d [key ]
164- if not isinstance (value , list ):
165- raise PylockValidationError (f"{ key !r} is not a list" )
166159 result = []
167160 for i , item in enumerate (value ):
168161 if not isinstance (item , dict ):
162+ raise PylockValidationError (f"Item { i } of { key !r} is not a table" )
163+ try :
164+ result .append (target_type .from_dict (item ))
165+ except Exception as e :
169166 raise PylockValidationError (
170- f"Item { i } in table { key !r} is not a dictionary"
171- )
172- result .append (expected_type .from_dict (item ))
167+ f"Error parsing item { i } of { key !r} : { e } "
168+ ) from e
173169 return result
174170
175171
176172def _get_required_list_of_objects (
177- d : Dict [str , Any ], expected_type : Type [PylockDataClassT ], key : str
178- ) -> List [PylockDataClassT ]:
173+ d : Dict [str , Any ], target_type : Type [FromDictProtocolT ], key : str
174+ ) -> List [FromDictProtocolT ]:
179175 """Get required list value from dictionary and convert items to dataclass."""
180- result = _get_list_of_objects (d , expected_type , key )
176+ result = _get_list_of_objects (d , target_type , key )
181177 if result is None :
182178 raise PylockRequiredKeyError (key )
183179 return result
@@ -356,9 +352,9 @@ def __post_init__(self) -> None:
356352 def from_dict (cls , d : Dict [str , Any ]) -> Self :
357353 package = cls (
358354 name = _get_required (d , str , "name" ),
359- version = _get_version ( d , "version" ),
360- requires_python = _get_specifier_set ( d , "requires-python" ),
361- marker = _get_marker ( d , "marker" ),
355+ version = _get_as ( d , str , Version , "version" ),
356+ requires_python = _get_as ( d , str , SpecifierSet , "requires-python" ),
357+ marker = _get_as ( d , str , Marker , "marker" ),
362358 vcs = _get_object (d , PackageVcs , "vcs" ),
363359 directory = _get_object (d , PackageDirectory , "directory" ),
364360 archive = _get_object (d , PackageArchive , "archive" ),
@@ -487,10 +483,10 @@ def to_dict(self) -> Dict[str, Any]:
487483 @classmethod
488484 def from_dict (cls , d : Dict [str , Any ]) -> Self :
489485 return cls (
490- lock_version = _get_required_version ( d , "lock-version" ),
491- environments = _get_list_of_markers ( d , "environments" ),
486+ lock_version = _get_required_as ( d , str , Version , "lock-version" ),
487+ environments = _get_list_as ( d , str , Marker , "environments" ),
492488 created_by = _get_required (d , str , "created-by" ),
493- requires_python = _get_specifier_set ( d , "requires-python" ),
489+ requires_python = _get_as ( d , str , SpecifierSet , "requires-python" ),
494490 packages = _get_required_list_of_objects (d , Package , "packages" ),
495491 )
496492
0 commit comments