17
17
)
18
18
19
19
from ._types import (
20
- VersionTuple ,
20
+ String ,
21
+ StringOrInt ,
21
22
VersionDict ,
22
23
VersionIterator ,
23
- String ,
24
+ VersionTuple ,
24
25
VersionPart ,
25
26
)
26
27
@@ -109,12 +110,28 @@ class Version:
109
110
"""
110
111
A semver compatible version class.
111
112
113
+ :param args: a tuple with version information. It can consist of:
114
+
115
+ * a maximum length of 5 items that comprehend the major,
116
+ minor, patch, prerelease, or build.
117
+ * a str or bytes string that contains a valid semver
118
+ version string.
112
119
:param major: version when you make incompatible API changes.
113
120
:param minor: version when you add functionality in
114
121
a backwards-compatible manner.
115
122
:param patch: version when you make backwards-compatible bug fixes.
116
123
:param prerelease: an optional prerelease string
117
124
:param build: an optional build string
125
+
126
+ This gives you some options to call the :class:`Version` class.
127
+ Precedence has the keyword arguments over the positional arguments.
128
+
129
+ >>> Version(1, 2, 3)
130
+ Version(major=1, minor=2, patch=3, prerelease=None, build=None)
131
+ >>> Version("2.3.4-pre.2")
132
+ Version(major=2, minor=3, patch=4, prerelease="pre.2", build=None)
133
+ >>> Version(major=2, minor=3, patch=4, build="build.2")
134
+ Version(major=2, minor=3, patch=4, prerelease=None, build="build.2")
118
135
"""
119
136
120
137
__slots__ = ("_major" , "_minor" , "_patch" , "_prerelease" , "_build" )
@@ -144,27 +161,92 @@ class Version:
144
161
145
162
def __init__ (
146
163
self ,
147
- major : SupportsInt ,
164
+ * args : Tuple [
165
+ Union [str , bytes , int ],
166
+ Optional [int ],
167
+ Optional [int ],
168
+ Optional [str ],
169
+ Optional [str ],
170
+ ],
171
+ major : SupportsInt = 0 ,
148
172
minor : SupportsInt = 0 ,
149
173
patch : SupportsInt = 0 ,
150
- prerelease : Union [ String , int ] = None ,
151
- build : Union [ String , int ] = None ,
174
+ prerelease : StringOrInt = None ,
175
+ build : StringOrInt = None ,
152
176
):
177
+ verlist = [None , None , None , None , None ]
178
+
179
+ if args and "." in str (args [0 ]):
180
+ # we have a version string as first argument
181
+ cls = self .__class__
182
+ v = cast (dict , cls ._parse (args [0 ])) # type: ignore
183
+ self ._major = int (v ["major" ])
184
+ self ._minor = int (v ["minor" ])
185
+ self ._patch = int (v ["patch" ])
186
+ self ._prerelease = v ["prerelease" ]
187
+ self ._build = v ["build" ]
188
+ return
189
+ if args and len (args ) > 5 :
190
+ raise ValueError ("You cannot pass more than 5 arguments to Version" )
191
+
192
+ for index , item in enumerate (args ):
193
+ verlist [index ] = args [index ] # type: ignore
194
+
153
195
# Build a dictionary of the arguments except prerelease and build
154
- version_parts = {"major" : int (major ), "minor" : int (minor ), "patch" : int (patch )}
196
+ try :
197
+ version_parts = {
198
+ # Prefer major, minor, and patch over args
199
+ "major" : int (major or verlist [0 ] or 0 ),
200
+ "minor" : int (minor or verlist [1 ] or 0 ),
201
+ "patch" : int (patch or verlist [2 ] or 0 ),
202
+ }
203
+ except ValueError :
204
+ raise ValueError (
205
+ "Expected integer or integer string for major, " "minor, or patch"
206
+ )
155
207
156
208
for name , value in version_parts .items ():
157
209
if value < 0 :
158
210
raise ValueError (
159
211
"{!r} is negative. A version can only be positive." .format (name )
160
212
)
161
213
214
+ prerelease = prerelease or verlist [3 ]
215
+ build = build or verlist [4 ]
216
+
162
217
self ._major = version_parts ["major" ]
163
218
self ._minor = version_parts ["minor" ]
164
219
self ._patch = version_parts ["patch" ]
165
220
self ._prerelease = None if prerelease is None else str (prerelease )
166
221
self ._build = None if build is None else str (build )
167
222
223
+ @classmethod
224
+ def _parse (cls , version : String ) -> Dict :
225
+ """
226
+ Parse version string to a Version instance.
227
+
228
+ .. versionchanged:: 2.11.0
229
+ Changed method from static to classmethod to
230
+ allow subclasses.
231
+
232
+ :param version: version string
233
+ :return: a new :class:`Version` instance
234
+ :raises ValueError: if version is invalid
235
+
236
+ >>> semver.Version.parse('3.4.5-pre.2+build.4')
237
+ Version(major=3, minor=4, patch=5, \
238
+ prerelease='pre.2', build='build.4')
239
+ """
240
+ if isinstance (version , bytes ):
241
+ version : str = version .decode ("UTF-8" ) # type: ignore
242
+ elif not isinstance (version , String .__args__ ): # type: ignore
243
+ raise TypeError (f"not expecting type { type (version )!r} " )
244
+ match = cls ._REGEX .match (cast (str , version ))
245
+ if match is None :
246
+ raise ValueError (f"{ version } is not valid SemVer string" ) # type: ignore
247
+
248
+ return cast (dict , match .groupdict ())
249
+
168
250
@property
169
251
def major (self ) -> int :
170
252
"""The major part of a version (read-only)."""
@@ -285,7 +367,7 @@ def bump_major(self) -> "Version":
285
367
Version(major=4, minor=0, patch=0, prerelease=None, build=None)
286
368
"""
287
369
cls = type (self )
288
- return cls (self ._major + 1 )
370
+ return cls (major = self ._major + 1 )
289
371
290
372
def bump_minor (self ) -> "Version" :
291
373
"""
@@ -299,7 +381,7 @@ def bump_minor(self) -> "Version":
299
381
Version(major=3, minor=5, patch=0, prerelease=None, build=None)
300
382
"""
301
383
cls = type (self )
302
- return cls (self ._major , self ._minor + 1 )
384
+ return cls (major = self ._major , minor = self ._minor + 1 )
303
385
304
386
def bump_patch (self ) -> "Version" :
305
387
"""
@@ -313,7 +395,7 @@ def bump_patch(self) -> "Version":
313
395
Version(major=3, minor=4, patch=6, prerelease=None, build=None)
314
396
"""
315
397
cls = type (self )
316
- return cls (self ._major , self ._minor , self ._patch + 1 )
398
+ return cls (major = self ._major , minor = self ._minor , patch = self ._patch + 1 )
317
399
318
400
def bump_prerelease (self , token : str = "rc" ) -> "Version" :
319
401
"""
@@ -330,7 +412,12 @@ def bump_prerelease(self, token: str = "rc") -> "Version":
330
412
"""
331
413
cls = type (self )
332
414
prerelease = cls ._increment_string (self ._prerelease or (token or "rc" ) + ".0" )
333
- return cls (self ._major , self ._minor , self ._patch , prerelease )
415
+ return cls (
416
+ major = self ._major ,
417
+ minor = self ._minor ,
418
+ patch = self ._patch ,
419
+ prerelease = prerelease ,
420
+ )
334
421
335
422
def bump_build (self , token : str = "build" ) -> "Version" :
336
423
"""
@@ -347,7 +434,13 @@ def bump_build(self, token: str = "build") -> "Version":
347
434
"""
348
435
cls = type (self )
349
436
build = cls ._increment_string (self ._build or (token or "build" ) + ".0" )
350
- return cls (self ._major , self ._minor , self ._patch , self ._prerelease , build )
437
+ return cls (
438
+ major = self ._major ,
439
+ minor = self ._minor ,
440
+ patch = self ._patch ,
441
+ prerelease = self ._prerelease ,
442
+ build = build ,
443
+ )
351
444
352
445
def compare (self , other : Comparable ) -> int :
353
446
"""
@@ -513,11 +606,11 @@ def __repr__(self) -> str:
513
606
return "%s(%s)" % (type (self ).__name__ , s )
514
607
515
608
def __str__ (self ) -> str :
516
- version = "%d.%d.%d" % ( self .major , self . minor , self .patch )
609
+ version = f" { self . major :d } . { self .minor :d } . { self .patch :d } "
517
610
if self .prerelease :
518
- version += "-%s" % self .prerelease
611
+ version += f"- { self .prerelease } "
519
612
if self .build :
520
- version += "+%s" % self .build
613
+ version += f"+ { self .build } "
521
614
return version
522
615
523
616
def __hash__ (self ) -> int :
@@ -533,7 +626,7 @@ def finalize_version(self) -> "Version":
533
626
'1.2.3'
534
627
"""
535
628
cls = type (self )
536
- return cls (self .major , self .minor , self .patch )
629
+ return cls (major = self .major , minor = self .minor , patch = self .patch )
537
630
538
631
def match (self , match_expr : str ) -> bool :
539
632
"""
@@ -598,13 +691,7 @@ def parse(cls, version: String) -> "Version":
598
691
Version(major=3, minor=4, patch=5, \
599
692
prerelease='pre.2', build='build.4')
600
693
"""
601
- version_str = ensure_str (version )
602
- match = cls ._REGEX .match (version_str )
603
- if match is None :
604
- raise ValueError (f"{ version_str } is not valid SemVer string" )
605
-
606
- matched_version_parts : Dict [str , Any ] = match .groupdict ()
607
-
694
+ matched_version_parts : Dict [str , Any ] = cls ._parse (version )
608
695
return cls (** matched_version_parts )
609
696
610
697
def replace (self , ** parts : Union [int , Optional [str ]]) -> "Version" :
0 commit comments