1717)
1818
1919from ._types import (
20- VersionTuple ,
20+ String ,
21+ StringOrInt ,
2122 VersionDict ,
2223 VersionIterator ,
23- String ,
24+ VersionTuple ,
2425 VersionPart ,
2526)
2627
@@ -109,12 +110,28 @@ class Version:
109110 """
110111 A semver compatible version class.
111112
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.
112119 :param major: version when you make incompatible API changes.
113120 :param minor: version when you add functionality in
114121 a backwards-compatible manner.
115122 :param patch: version when you make backwards-compatible bug fixes.
116123 :param prerelease: an optional prerelease string
117124 :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")
118135 """
119136
120137 __slots__ = ("_major" , "_minor" , "_patch" , "_prerelease" , "_build" )
@@ -144,27 +161,92 @@ class Version:
144161
145162 def __init__ (
146163 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 ,
148172 minor : SupportsInt = 0 ,
149173 patch : SupportsInt = 0 ,
150- prerelease : Union [ String , int ] = None ,
151- build : Union [ String , int ] = None ,
174+ prerelease : StringOrInt = None ,
175+ build : StringOrInt = None ,
152176 ):
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+
153195 # 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+ )
155207
156208 for name , value in version_parts .items ():
157209 if value < 0 :
158210 raise ValueError (
159211 "{!r} is negative. A version can only be positive." .format (name )
160212 )
161213
214+ prerelease = prerelease or verlist [3 ]
215+ build = build or verlist [4 ]
216+
162217 self ._major = version_parts ["major" ]
163218 self ._minor = version_parts ["minor" ]
164219 self ._patch = version_parts ["patch" ]
165220 self ._prerelease = None if prerelease is None else str (prerelease )
166221 self ._build = None if build is None else str (build )
167222
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+
168250 @property
169251 def major (self ) -> int :
170252 """The major part of a version (read-only)."""
@@ -285,7 +367,7 @@ def bump_major(self) -> "Version":
285367 Version(major=4, minor=0, patch=0, prerelease=None, build=None)
286368 """
287369 cls = type (self )
288- return cls (self ._major + 1 )
370+ return cls (major = self ._major + 1 )
289371
290372 def bump_minor (self ) -> "Version" :
291373 """
@@ -299,7 +381,7 @@ def bump_minor(self) -> "Version":
299381 Version(major=3, minor=5, patch=0, prerelease=None, build=None)
300382 """
301383 cls = type (self )
302- return cls (self ._major , self ._minor + 1 )
384+ return cls (major = self ._major , minor = self ._minor + 1 )
303385
304386 def bump_patch (self ) -> "Version" :
305387 """
@@ -313,7 +395,7 @@ def bump_patch(self) -> "Version":
313395 Version(major=3, minor=4, patch=6, prerelease=None, build=None)
314396 """
315397 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 )
317399
318400 def bump_prerelease (self , token : str = "rc" ) -> "Version" :
319401 """
@@ -330,7 +412,12 @@ def bump_prerelease(self, token: str = "rc") -> "Version":
330412 """
331413 cls = type (self )
332414 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+ )
334421
335422 def bump_build (self , token : str = "build" ) -> "Version" :
336423 """
@@ -347,7 +434,13 @@ def bump_build(self, token: str = "build") -> "Version":
347434 """
348435 cls = type (self )
349436 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+ )
351444
352445 def compare (self , other : Comparable ) -> int :
353446 """
@@ -513,11 +606,11 @@ def __repr__(self) -> str:
513606 return "%s(%s)" % (type (self ).__name__ , s )
514607
515608 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 } "
517610 if self .prerelease :
518- version += "-%s" % self .prerelease
611+ version += f"- { self .prerelease } "
519612 if self .build :
520- version += "+%s" % self .build
613+ version += f"+ { self .build } "
521614 return version
522615
523616 def __hash__ (self ) -> int :
@@ -533,7 +626,7 @@ def finalize_version(self) -> "Version":
533626 '1.2.3'
534627 """
535628 cls = type (self )
536- return cls (self .major , self .minor , self .patch )
629+ return cls (major = self .major , minor = self .minor , patch = self .patch )
537630
538631 def match (self , match_expr : str ) -> bool :
539632 """
@@ -598,13 +691,7 @@ def parse(cls, version: String) -> "Version":
598691 Version(major=3, minor=4, patch=5, \
599692 prerelease='pre.2', build='build.4')
600693 """
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 )
608695 return cls (** matched_version_parts )
609696
610697 def replace (self , ** parts : Union [int , Optional [str ]]) -> "Version" :
0 commit comments