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,27 +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
- resolved_revision : Optional [str ] = None ,
78
- resolved_revision_type : Optional [str ] = None ,
79
- ) -> None :
80
- self .vcs = vcs
81
- self .requested_revision = requested_revision
82
- self .commit_id = commit_id
83
- self .resolved_revision = resolved_revision
84
- self .resolved_revision_type = resolved_revision_type
105
+ name : ClassVar [str ] = "vcs_info"
85
106
86
107
@classmethod
87
108
def _from_dict (cls , d : Optional [Dict [str , Any ]]) -> Optional ["VcsInfo" ]:
@@ -105,14 +126,11 @@ def _to_dict(self) -> Dict[str, Any]:
105
126
)
106
127
107
128
108
- class ArchiveInfo :
109
- name = "archive_info"
129
+ @dataclass (frozen = True )
130
+ class ArchiveInfo (InfoType ):
131
+ hash : Optional [str ] = None
110
132
111
- def __init__ (
112
- self ,
113
- hash : Optional [str ] = None ,
114
- ) -> None :
115
- self .hash = hash
133
+ name : ClassVar [str ] = "archive_info"
116
134
117
135
@classmethod
118
136
def _from_dict (cls , d : Optional [Dict [str , Any ]]) -> Optional ["ArchiveInfo" ]:
@@ -124,14 +142,11 @@ def _to_dict(self) -> Dict[str, Any]:
124
142
return _filter_none (hash = self .hash )
125
143
126
144
127
- class DirInfo :
128
- name = "dir_info"
145
+ @dataclass (frozen = True )
146
+ class DirInfo (InfoType ):
147
+ editable : bool = False
129
148
130
- def __init__ (
131
- self ,
132
- editable : bool = False ,
133
- ) -> None :
134
- self .editable = editable
149
+ name : ClassVar [str ] = "dir_info"
135
150
136
151
@classmethod
137
152
def _from_dict (cls , d : Optional [Dict [str , Any ]]) -> Optional ["DirInfo" ]:
@@ -143,19 +158,11 @@ def _to_dict(self) -> Dict[str, Any]:
143
158
return _filter_none (editable = self .editable or None )
144
159
145
160
146
- InfoType = Union [ArchiveInfo , DirInfo , VcsInfo ]
147
-
148
-
161
+ @dataclass (frozen = True )
149
162
class DirectUrl :
150
- def __init__ (
151
- self ,
152
- url : str ,
153
- info : InfoType ,
154
- subdirectory : Optional [str ] = None ,
155
- ) -> None :
156
- self .url = url
157
- self .info = info
158
- self .subdirectory = subdirectory
163
+ url : str
164
+ info : InfoType
165
+ subdirectory : Optional [str ] = None
159
166
160
167
def _remove_auth_from_netloc (self , netloc : str ) -> str :
161
168
if "@" not in netloc :
@@ -192,13 +199,7 @@ def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl":
192
199
return DirectUrl (
193
200
url = _get_required (d , str , "url" ),
194
201
subdirectory = _get (d , str , "subdirectory" ),
195
- info = _exactly_one_of (
196
- [
197
- ArchiveInfo ._from_dict (_get (d , dict , "archive_info" )),
198
- DirInfo ._from_dict (_get (d , dict , "dir_info" )),
199
- VcsInfo ._from_dict (_get (d , dict , "vcs_info" )),
200
- ]
201
- ),
202
+ info = InfoType .from_dict (d ),
202
203
)
203
204
204
205
def to_dict (self ) -> Dict [str , Any ]:
0 commit comments