17
17
)
18
18
19
19
from 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
23
23
from pip ._vendor .typing_extensions import Self
24
24
25
25
from pip ._internal .models .direct_url import ArchiveInfo , DirInfo , VcsInfo
28
28
from pip ._internal .utils .urls import url_to_path
29
29
30
30
T = TypeVar ("T" )
31
+ T2 = TypeVar ("T2" )
31
32
32
33
33
- class PylockDataClass (Protocol ):
34
+ class FromDictProtocol (Protocol ):
34
35
@classmethod
35
36
def from_dict (cls , d : Dict [str , Any ]) -> Self :
36
37
pass
37
38
38
39
39
- PylockDataClassT = TypeVar ("PylockDataClassT " , bound = PylockDataClass )
40
+ FromDictProtocolT = TypeVar ("FromDictProtocolT " , bound = FromDictProtocol )
40
41
41
42
PYLOCK_FILE_NAME_RE = re .compile (r"^pylock\.([^.]+)\.toml$" )
42
43
@@ -67,12 +68,12 @@ def _toml_dict_factory(data: List[Tuple[str, Any]]) -> Dict[str, Any]:
67
68
68
69
def _get (d : Dict [str , Any ], expected_type : Type [T ], key : str ) -> Optional [T ]:
69
70
"""Get value from dictionary and verify expected type."""
70
- if key not in d :
71
+ value = d .get (key )
72
+ if value is None :
71
73
return None
72
- value = d [key ]
73
74
if not isinstance (value , expected_type ):
74
75
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 } )"
76
77
)
77
78
return value
78
79
@@ -85,99 +86,94 @@ def _get_required(d: Dict[str, Any], expected_type: Type[T], key: str) -> T:
85
86
return value
86
87
87
88
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 )
90
97
if value is None :
91
98
return None
92
99
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
96
103
97
104
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 )
100
111
if value is None :
101
112
raise PylockRequiredKeyError (key )
102
113
return value
103
114
104
115
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 ]]:
116
119
"""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 :
118
122
return None
119
- value = d [key ]
120
- if not isinstance (value , list ):
121
- raise PylockValidationError (f"{ key !r} is not a list" )
122
123
result = []
123
124
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 ):
129
126
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 } )"
131
129
)
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
132
136
return result
133
137
134
138
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
-
145
139
def _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 ]:
148
142
"""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 :
150
145
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
155
150
156
151
157
152
def _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 ]]:
160
155
"""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 :
162
158
return None
163
- value = d [key ]
164
- if not isinstance (value , list ):
165
- raise PylockValidationError (f"{ key !r} is not a list" )
166
159
result = []
167
160
for i , item in enumerate (value ):
168
161
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 :
169
166
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
173
169
return result
174
170
175
171
176
172
def _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 ]:
179
175
"""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 )
181
177
if result is None :
182
178
raise PylockRequiredKeyError (key )
183
179
return result
@@ -356,9 +352,9 @@ def __post_init__(self) -> None:
356
352
def from_dict (cls , d : Dict [str , Any ]) -> Self :
357
353
package = cls (
358
354
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" ),
362
358
vcs = _get_object (d , PackageVcs , "vcs" ),
363
359
directory = _get_object (d , PackageDirectory , "directory" ),
364
360
archive = _get_object (d , PackageArchive , "archive" ),
@@ -490,10 +486,10 @@ def to_dict(self) -> Dict[str, Any]:
490
486
@classmethod
491
487
def from_dict (cls , d : Dict [str , Any ]) -> Self :
492
488
return cls (
493
- lock_version = _get_required_version ( d , "lock-version" ),
494
- environments = _get_list_of_markers ( d , "environments" ),
489
+ lock_version = _get_required_as ( d , str , Version , "lock-version" ),
490
+ environments = _get_list_as ( d , str , Marker , "environments" ),
495
491
created_by = _get_required (d , str , "created-by" ),
496
- requires_python = _get_specifier_set ( d , "requires-python" ),
492
+ requires_python = _get_as ( d , str , SpecifierSet , "requires-python" ),
497
493
packages = _get_required_list_of_objects (d , Package , "packages" ),
498
494
)
499
495
0 commit comments