1
1
""" PEP 610 """
2
+ import abc
2
3
import json
3
4
import re
4
5
import urllib .parse
5
- from typing import Any , Dict , Iterable , Optional , Type , TypeVar , Union
6
+ from dataclasses import dataclass
7
+ from typing import Any , ClassVar , Dict , Iterable , Optional , Type , TypeVar
6
8
7
9
__all__ = [
8
10
"DirectUrl" ,
@@ -47,8 +49,39 @@ def _get_required(
47
49
return value
48
50
49
51
50
- def _exactly_one_of (infos : Iterable [Optional ["InfoType" ]]) -> "InfoType" :
51
- infos = [info for info in infos if info is not None ]
52
+ def _filter_none (** kwargs : Any ) -> Dict [str , Any ]:
53
+ """Make dict excluding None values."""
54
+ return {k : v for k , v in kwargs .items () if v is not None }
55
+
56
+
57
+ class InfoType (metaclass = abc .ABCMeta ):
58
+ """Superclass for the types of metadata that can be stored within a "direct URL"."""
59
+
60
+ name : ClassVar [str ]
61
+
62
+ @classmethod
63
+ @abc .abstractmethod
64
+ def _from_dict (cls : Type [T ], d : Optional [Dict [str , Any ]]) -> Optional [T ]:
65
+ """Parse an instance of this class from a JSON-serializable dict."""
66
+
67
+ @abc .abstractmethod
68
+ def _to_dict (self ) -> Dict [str , Any ]:
69
+ """Produce a JSON-serializable dict which can be parsed with `._from_dict()`."""
70
+
71
+ @classmethod
72
+ def from_dict (cls , d : Dict [str , Any ]) -> "InfoType" :
73
+ """Parse exactly one of the known subclasses from the dict `d`."""
74
+ return _exactly_one_of (
75
+ [
76
+ ArchiveInfo ._from_dict (_get (d , dict , "archive_info" )),
77
+ DirInfo ._from_dict (_get (d , dict , "dir_info" )),
78
+ VcsInfo ._from_dict (_get (d , dict , "vcs_info" )),
79
+ ]
80
+ )
81
+
82
+
83
+ def _exactly_one_of (infos : Iterable [Optional [InfoType ]]) -> InfoType :
84
+ infos = list (filter (None , infos ))
52
85
if not infos :
53
86
raise DirectUrlValidationError (
54
87
"missing one of archive_info, dir_info, vcs_info"
@@ -61,23 +94,15 @@ def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType":
61
94
return infos [0 ]
62
95
63
96
64
- def _filter_none ( ** kwargs : Any ) -> Dict [ str , Any ]:
65
- """Make dict excluding None values."""
66
- return { k : v for k , v in kwargs . items () if v is not None }
67
-
68
-
69
- class VcsInfo :
70
- name = "vcs_info"
97
+ @ dataclass ( frozen = True )
98
+ class VcsInfo ( InfoType ):
99
+ vcs : str
100
+ commit_id : str
101
+ requested_revision : Optional [ str ] = None
102
+ resolved_revision : Optional [ str ] = None
103
+ resolved_revision_type : Optional [ str ] = None
71
104
72
- def __init__ (
73
- self ,
74
- vcs : str ,
75
- commit_id : str ,
76
- requested_revision : Optional [str ] = None ,
77
- ) -> None :
78
- self .vcs = vcs
79
- self .requested_revision = requested_revision
80
- self .commit_id = commit_id
105
+ name : ClassVar [str ] = "vcs_info"
81
106
82
107
@classmethod
83
108
def _from_dict (cls , d : Optional [Dict [str , Any ]]) -> Optional ["VcsInfo" ]:
@@ -97,14 +122,11 @@ def _to_dict(self) -> Dict[str, Any]:
97
122
)
98
123
99
124
100
- class ArchiveInfo :
101
- name = "archive_info"
125
+ @dataclass (frozen = True )
126
+ class ArchiveInfo (InfoType ):
127
+ hash : Optional [str ] = None
102
128
103
- def __init__ (
104
- self ,
105
- hash : Optional [str ] = None ,
106
- ) -> None :
107
- self .hash = hash
129
+ name : ClassVar [str ] = "archive_info"
108
130
109
131
@classmethod
110
132
def _from_dict (cls , d : Optional [Dict [str , Any ]]) -> Optional ["ArchiveInfo" ]:
@@ -116,14 +138,11 @@ def _to_dict(self) -> Dict[str, Any]:
116
138
return _filter_none (hash = self .hash )
117
139
118
140
119
- class DirInfo :
120
- name = "dir_info"
141
+ @dataclass (frozen = True )
142
+ class DirInfo (InfoType ):
143
+ editable : bool = False
121
144
122
- def __init__ (
123
- self ,
124
- editable : bool = False ,
125
- ) -> None :
126
- self .editable = editable
145
+ name : ClassVar [str ] = "dir_info"
127
146
128
147
@classmethod
129
148
def _from_dict (cls , d : Optional [Dict [str , Any ]]) -> Optional ["DirInfo" ]:
@@ -135,19 +154,11 @@ def _to_dict(self) -> Dict[str, Any]:
135
154
return _filter_none (editable = self .editable or None )
136
155
137
156
138
- InfoType = Union [ArchiveInfo , DirInfo , VcsInfo ]
139
-
140
-
157
+ @dataclass (frozen = True )
141
158
class DirectUrl :
142
- def __init__ (
143
- self ,
144
- url : str ,
145
- info : InfoType ,
146
- subdirectory : Optional [str ] = None ,
147
- ) -> None :
148
- self .url = url
149
- self .info = info
150
- self .subdirectory = subdirectory
159
+ url : str
160
+ info : InfoType
161
+ subdirectory : Optional [str ] = None
151
162
152
163
def _remove_auth_from_netloc (self , netloc : str ) -> str :
153
164
if "@" not in netloc :
@@ -184,13 +195,7 @@ def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl":
184
195
return DirectUrl (
185
196
url = _get_required (d , str , "url" ),
186
197
subdirectory = _get (d , str , "subdirectory" ),
187
- info = _exactly_one_of (
188
- [
189
- ArchiveInfo ._from_dict (_get (d , dict , "archive_info" )),
190
- DirInfo ._from_dict (_get (d , dict , "dir_info" )),
191
- VcsInfo ._from_dict (_get (d , dict , "vcs_info" )),
192
- ]
193
- ),
198
+ info = InfoType .from_dict (d ),
194
199
)
195
200
196
201
def to_dict (self ) -> Dict [str , Any ]:
0 commit comments