version-query 1.6.1__tar.gz → 1.6.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {version_query-1.6.1/version_query.egg-info → version_query-1.6.2}/PKG-INFO +7 -7
  2. {version_query-1.6.1 → version_query-1.6.2}/README.rst +3 -3
  3. {version_query-1.6.1 → version_query-1.6.2}/test/test_version.py +8 -5
  4. {version_query-1.6.1 → version_query-1.6.2}/version_query/git_query.py +12 -6
  5. version_query-1.6.2/version_query/parser.py +53 -0
  6. version_query-1.6.2/version_query/patterns.py +39 -0
  7. {version_query-1.6.1 → version_query-1.6.2}/version_query/version.py +70 -123
  8. {version_query-1.6.1 → version_query-1.6.2/version_query.egg-info}/PKG-INFO +7 -7
  9. {version_query-1.6.1 → version_query-1.6.2}/version_query.egg-info/SOURCES.txt +4 -0
  10. {version_query-1.6.1 → version_query-1.6.2}/LICENSE +0 -0
  11. {version_query-1.6.1 → version_query-1.6.2}/MANIFEST.in +0 -0
  12. {version_query-1.6.1 → version_query-1.6.2}/NOTICE +0 -0
  13. {version_query-1.6.1 → version_query-1.6.2}/pyproject.toml +0 -0
  14. {version_query-1.6.1 → version_query-1.6.2}/requirements.txt +0 -0
  15. {version_query-1.6.1 → version_query-1.6.2}/requirements_test.txt +0 -0
  16. {version_query-1.6.1 → version_query-1.6.2}/setup.cfg +0 -0
  17. {version_query-1.6.1 → version_query-1.6.2}/setup.py +0 -0
  18. {version_query-1.6.1 → version_query-1.6.2}/test/__init__.py +0 -0
  19. {version_query-1.6.1 → version_query-1.6.2}/test/examples.py +0 -0
  20. {version_query-1.6.1 → version_query-1.6.2}/test/test_git.py +0 -0
  21. {version_query-1.6.1 → version_query-1.6.2}/test/test_packaging.py +0 -0
  22. {version_query-1.6.1 → version_query-1.6.2}/test/test_query.py +0 -0
  23. {version_query-1.6.1 → version_query-1.6.2}/version_query/__init__.py +0 -0
  24. {version_query-1.6.1 → version_query-1.6.2}/version_query/__main__.py +0 -0
  25. {version_query-1.6.1 → version_query-1.6.2}/version_query/_version.py +0 -0
  26. {version_query-1.6.1 → version_query-1.6.2}/version_query/main.py +0 -0
  27. {version_query-1.6.1 → version_query-1.6.2}/version_query/py.typed +0 -0
  28. {version_query-1.6.1 → version_query-1.6.2}/version_query/py_query.py +0 -0
  29. {version_query-1.6.1 → version_query-1.6.2}/version_query/query.py +0 -0
  30. {version_query-1.6.1 → version_query-1.6.2}/version_query.egg-info/dependency_links.txt +0 -0
  31. {version_query-1.6.1 → version_query-1.6.2}/version_query.egg-info/requires.txt +0 -0
  32. {version_query-1.6.1 → version_query-1.6.2}/version_query.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: version-query
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary: Zero-overhead package versioning for Python.
5
5
  Home-page: https://github.com/mbdevpl/version-query
6
6
  Download-URL:
@@ -81,7 +81,7 @@ Zero-overhead package versioning for Python.
81
81
  :alt: grade from Codacy
82
82
 
83
83
  .. image:: https://img.shields.io/github/license/mbdevpl/version-query.svg
84
- :target: https://github.com/mbdevpl/version-query/blob/v1.6.1/NOTICE
84
+ :target: https://github.com/mbdevpl/version-query/blob/v1.6.2/NOTICE
85
85
  :alt: license
86
86
 
87
87
  Package versioning toolkit for Python which relies on git tags, git history traversal
@@ -361,9 +361,9 @@ Incrementing any version component clears all existing following components.
361
361
  Examples of how version is incremented:
362
362
 
363
363
  * for ``1.5``, incrementing ``<major>`` results in ``2.0``;
364
- * for ``1.5.1-2.4``, ``<minor>``++ results in ``1.6``;
365
- * ``1.5.1-2.4``, ``<patch>``++, ``1.5.2``;
366
- * ``1.5.1``, ``<major>``+=3, ``4.0.0``.
364
+ * for ``1.5.1-2.4``, ``<minor>`` ++ results in ``1.6``;
365
+ * ``1.5.1-2.4``, ``<patch>`` ++, ``1.5.2``;
366
+ * ``1.5.1``, ``<major>`` += 3, ``4.0.0``.
367
367
 
368
368
 
369
369
  API details
@@ -491,8 +491,8 @@ Requirements
491
491
 
492
492
  Python version 3.9 or later.
493
493
 
494
- Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.6.1/requirements.txt>`_.
494
+ Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.6.2/requirements.txt>`_.
495
495
 
496
- Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.6.1/requirements_test.txt>`_.
496
+ Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.6.2/requirements_test.txt>`_.
497
497
 
498
498
  Tested on Linux, macOS and Windows.
@@ -308,9 +308,9 @@ Incrementing any version component clears all existing following components.
308
308
  Examples of how version is incremented:
309
309
 
310
310
  * for ``1.5``, incrementing ``<major>`` results in ``2.0``;
311
- * for ``1.5.1-2.4``, ``<minor>``++ results in ``1.6``;
312
- * ``1.5.1-2.4``, ``<patch>``++, ``1.5.2``;
313
- * ``1.5.1``, ``<major>``+=3, ``4.0.0``.
311
+ * for ``1.5.1-2.4``, ``<minor>`` ++ results in ``1.6``;
312
+ * ``1.5.1-2.4``, ``<patch>`` ++, ``1.5.2``;
313
+ * ``1.5.1``, ``<major>`` += 3, ``4.0.0``.
314
314
 
315
315
 
316
316
  API details
@@ -25,19 +25,21 @@ class Tests(unittest.TestCase):
25
25
  with self.subTest(version_str=version_str, version_tuple=version_tuple):
26
26
 
27
27
  py_version = \
28
- pkg_resources.parse_version(version_str) # type: packaging.version.Version
29
- # self.assertIsInstance(
30
- # py_version, packaging.version.Version, msg=(type(py_version), py_version))
28
+ pkg_resources.parse_version(version_str)
31
29
  _LOG.debug('packaging parsed version string %s into %s: %s',
32
30
  repr(version_str), type(py_version), py_version)
31
+ # self.assertIsInstance(
32
+ # py_version, packaging.version.Version, msg=(type(py_version), py_version))
33
33
 
34
34
  try:
35
- sem_version = semver.VersionInfo.parse(version_str) # type: semver.VersionInfo
35
+ sem_version = semver.VersionInfo.parse(version_str)
36
36
  except ValueError:
37
37
  _LOG.debug('semver could not parse version string %s', repr(version_str))
38
38
  else:
39
39
  _LOG.debug('semver parsed version string %s into %s: %s',
40
40
  repr(version_str), type(sem_version), sem_version)
41
+ # self.assertIsInstance(
42
+ # sem_version, semver.VersionInfo, msg=(type(sem_version), sem_version))
41
43
 
42
44
  self.assertEqual(Version.from_str(version_str).to_tuple(), version_tuple)
43
45
 
@@ -76,7 +78,7 @@ class Tests(unittest.TestCase):
76
78
  version_tuple = case_to_version_tuple(args, kwargs)
77
79
  with self.subTest(version_str=version_str, version_tuple=version_tuple):
78
80
  try:
79
- sem_version = semver.VersionInfo.parse(version_str) # type: semver.VersionInfo
81
+ sem_version = semver.VersionInfo.parse(version_str)
80
82
  except ValueError:
81
83
  continue
82
84
  else:
@@ -123,6 +125,7 @@ class Tests(unittest.TestCase):
123
125
  self.assertIsInstance(version.local, tuple)
124
126
 
125
127
  def test_init_bad(self):
128
+ # pylint: disable = redefined-variable-type
126
129
  for (args, kwargs), exception in BAD_INIT_CASES.items():
127
130
  with self.subTest(args=args, kwargs=kwargs, exception=exception):
128
131
  with self.assertRaises(exception):
@@ -40,6 +40,17 @@ def _git_version_tags(repo: git.Repo) -> t.Mapping[git.Tag, Version]:
40
40
  return versions
41
41
 
42
42
 
43
+ def _git_version_tag_commits(
44
+ version_tags: t.Iterable[git.Tag]) -> t.Mapping[git.objects.Commit, t.Set[git.Tag]]:
45
+ version_tag_commits: t.Dict[git.objects.Commit, t.Set[git.Tag]] = {}
46
+ for tag in version_tags:
47
+ _commit = tag.commit
48
+ if _commit not in version_tag_commits:
49
+ version_tag_commits[_commit] = set()
50
+ version_tag_commits[_commit].add(tag)
51
+ return version_tag_commits
52
+
53
+
43
54
  def _latest_git_version_tag_on_branches(
44
55
  repo: git.Repo, assume_if_none: bool, commit: git.objects.Commit, commit_distance: int,
45
56
  skip_commits: t.Set[git.objects.Commit]) -> t.Union[int, t.Tuple[
@@ -80,12 +91,7 @@ def _latest_git_version_tag(
80
91
  t.Optional[git.objects.Commit], t.Optional[git.TagReference], t.Optional[Version], int]:
81
92
  """Return (commit, tag at that commit if any, latest version, distance from the version)."""
82
93
  version_tags = _git_version_tags(repo)
83
- version_tag_commits: t.Dict[git.objects.Commit, set] = {}
84
- for tag, version in version_tags.items():
85
- _commit = tag.commit
86
- if _commit not in version_tag_commits:
87
- version_tag_commits[_commit] = set()
88
- version_tag_commits[_commit].add(tag)
94
+ version_tag_commits = _git_version_tag_commits(version_tags.keys())
89
95
  current_version_tags = {}
90
96
  commit = None
91
97
  if skip_commits is None:
@@ -0,0 +1,53 @@
1
+ """Functions for parsing version strings into their components and verifying the format."""
2
+
3
+ import logging
4
+ import typing as t
5
+
6
+ from . import patterns
7
+
8
+ _LOG = logging.getLogger(__name__)
9
+
10
+
11
+ def parse_release_str(release: str) -> tuple:
12
+ """Parse a release string into major, minor, and patch version numbers."""
13
+ match = patterns.RELEASE.fullmatch(release)
14
+ assert match is not None
15
+ major_match = match.group('major')
16
+ assert major_match is not None
17
+ major = int(major_match)
18
+ minor_match = match.group('minor')
19
+ if minor_match is not None:
20
+ minor = int(minor_match)
21
+ else:
22
+ minor = None
23
+ patch_match = match.group('patch')
24
+ if patch_match is not None:
25
+ patch = int(patch_match)
26
+ else:
27
+ patch = None
28
+ return major, minor, patch
29
+
30
+
31
+ def parse_pre_release_str(pre_release: str) -> t.Sequence[
32
+ t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]]:
33
+ """Parse a pre-release string into a sequence of tuples."""
34
+ parts = patterns.PRE_RELEASE.findall(pre_release)
35
+ _LOG.debug('parsed pre-release string %s into %s', repr(pre_release), parts)
36
+ tuples = []
37
+ for part in parts:
38
+ match = patterns.PRE_RELEASE_PART.fullmatch(part)
39
+ assert match is not None
40
+ pre_patch_match = match.group('prepatch')
41
+ if pre_patch_match is not None:
42
+ pre_patch = int(pre_patch_match)
43
+ else:
44
+ pre_patch = None
45
+ tuples.append((match.group('preseparator'), match.group('pretype'), pre_patch))
46
+ return tuples
47
+
48
+
49
+ def parse_local_str(local: str) -> tuple:
50
+ """Parse a local version suffix string into a sequence."""
51
+ match = patterns.LOCAL.fullmatch(local)
52
+ assert match is not None
53
+ return tuple(_ for _ in match.groups() if _ is not None)
@@ -0,0 +1,39 @@
1
+ """Patterns for recognising parts of version strings and parsing them."""
2
+
3
+ import re
4
+
5
+ # pylint: disable = consider-using-f-string
6
+
7
+ _NUMBER = r'(?:0|[123456789][0123456789]*)'
8
+ # _SHA = r'[0123456789abcdef]+'
9
+ _LETTERS = r'(?:[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]+)'
10
+ LETTERS = re.compile(_LETTERS)
11
+ _ALPHANUMERIC = r'(?:[0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]+)'
12
+ ALPHANUMERIC = re.compile(_ALPHANUMERIC)
13
+ _SEP = r'(?:[\.-])'
14
+
15
+ _RELEASE_PARTS = \
16
+ r'(?P<major>{n})(?:\.(?P<minor>{n}))?(?:\.(?P<patch>{n}))?'.format(n=_NUMBER)
17
+ RELEASE = re.compile(_RELEASE_PARTS)
18
+
19
+ _PRE_SEPARATOR = rf'(?P<preseparator>{_SEP})'
20
+ _PRE_TYPE = rf'(?P<pretype>{_LETTERS})'
21
+ _PRE_PATCH = rf'(?P<prepatch>{_NUMBER})'
22
+ _PRE_RELEASE_PART = rf'{_PRE_SEPARATOR}?{_PRE_TYPE}?{_PRE_PATCH}?'
23
+ PRE_RELEASE_PART = re.compile(_PRE_RELEASE_PART)
24
+ _PRE_RELEASE_PARTS = r'(?:{0}{2})|(?:{0}?{1}{2}?)'.format(_SEP, _LETTERS, _NUMBER)
25
+ PRE_RELEASE = re.compile(_PRE_RELEASE_PARTS)
26
+ # PRE_RELEASE_CHECK = re.compile(rf'(?:{_PRE_RELEASE_PARTS})+')
27
+
28
+ _LOCAL_SEPARATOR = rf'({_SEP})'
29
+ _LOCAL_PART = rf'({_ALPHANUMERIC})'
30
+ _LOCAL_PARTS = rf'\+{_LOCAL_PART}(?:{_LOCAL_SEPARATOR}{_LOCAL_PART})*'
31
+ LOCAL = re.compile(_LOCAL_PARTS)
32
+
33
+
34
+ _RELEASE = r'(?P<release>{n}(?:\.{n})?(?:\.{n})?)'.format(n=_NUMBER)
35
+ _PRE_RELEASE = r'(?P<prerelease>(?:(?:{0}{2})|(?:{0}?{1}{2}?))+)'.format(_SEP, _LETTERS, _NUMBER)
36
+ _LOCAL = r'(?P<local>\+{0}([\.-]{0})*)'.format(_ALPHANUMERIC)
37
+ # _NAMED_PARTS_COUNT = 3 + 3
38
+ _VERSION = rf'{_RELEASE}{_PRE_RELEASE}?{_LOCAL}?'
39
+ VERSION = re.compile(_VERSION)
@@ -4,12 +4,14 @@ import collections.abc
4
4
  import enum
5
5
  import itertools
6
6
  import logging
7
- import re
8
7
  import typing as t
9
8
 
10
9
  import packaging.version
11
10
  import semver
12
11
 
12
+ from . import patterns
13
+ from .parser import parse_release_str, parse_pre_release_str, parse_local_str
14
+
13
15
  _LOG = logging.getLogger(__name__)
14
16
 
15
17
  PY_PRE_RELEASE_INDICATORS = {'a', 'b', 'c', 'rc'}
@@ -19,6 +21,8 @@ PY_PRE_RELEASE_INDICATORS = {'a', 'b', 'c', 'rc'}
19
21
  class VersionComponent(enum.IntEnum):
20
22
  """Enumeration of standard version components."""
21
23
 
24
+ # pylint: disable = invalid-name
25
+
22
26
  Major = 1 << 1
23
27
  Minor = 1 << 2
24
28
  Patch = 1 << 3
@@ -29,96 +33,21 @@ class VersionComponent(enum.IntEnum):
29
33
 
30
34
 
31
35
  def _version_tuple_checker(version_tuple, flags):
32
- return all([(_ is not None if flag else _ is None) for _, flag in zip(version_tuple, flags)])
36
+ return all((_ is not None if flag else _ is None) for _, flag in zip(version_tuple, flags))
33
37
 
34
38
 
35
- class Version(collections.abc.Hashable): # pylint: disable = too-many-public-methods
39
+ class Version(collections.abc.Hashable):
36
40
  """For storing and manipulating version information.
37
41
 
38
42
  Definitions of acceptable version formats are provided in readme.
39
43
  """
40
44
 
41
- _re_number = r'(?:0|[123456789][0123456789]*)'
42
- # _re_sha = r'[0123456789abcdef]+'
43
- _re_letters = r'(?:[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]+)'
44
- _pattern_letters = re.compile(_re_letters)
45
- _re_alphanumeric = r'(?:[0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]+)'
46
- _pattern_alphanumeric = re.compile(_re_alphanumeric)
47
- _re_sep = r'(?:[\.-])'
48
-
49
- _re_release_parts = \
50
- r'(?P<major>{n})(?:\.(?P<minor>{n}))?(?:\.(?P<patch>{n}))?'.format(n=_re_number)
51
- _pattern_release = re.compile(_re_release_parts)
52
-
53
- @classmethod
54
- def _parse_release_str(cls, release: str) -> tuple:
55
- match = cls._pattern_release.fullmatch(release)
56
- assert match is not None
57
- major_match = match.group('major')
58
- assert major_match is not None
59
- major = int(major_match)
60
- minor_match = match.group('minor')
61
- if minor_match is not None:
62
- minor = int(minor_match)
63
- else:
64
- minor = None
65
- patch_match = match.group('patch')
66
- if patch_match is not None:
67
- patch = int(patch_match)
68
- else:
69
- patch = None
70
- return major, minor, patch
71
-
72
- _re_pre_separator = rf'(?P<preseparator>{_re_sep})'
73
- _re_pre_type = rf'(?P<pretype>{_re_letters})'
74
- _re_pre_patch = rf'(?P<prepatch>{_re_number})'
75
- _re_pre_release_part = rf'{_re_pre_separator}?{_re_pre_type}?{_re_pre_patch}?'
76
- _pattern_pre_release_part = re.compile(_re_pre_release_part)
77
- _re_pre_release_parts = r'(?:{0}{2})|(?:{0}?{1}{2}?)'.format(_re_sep, _re_letters, _re_number)
78
- _pattern_pre_release = re.compile(_re_pre_release_parts)
79
- _pattern_pre_release_check = re.compile(rf'(?:{_re_pre_release_parts})+')
80
-
81
- @classmethod
82
- def _parse_pre_release_str(cls, pre_release: str) -> t.Sequence[
83
- t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]]:
84
- parts = cls._pattern_pre_release.findall(pre_release)
85
- _LOG.debug('parsed pre-release string %s into %s',
86
- repr(pre_release), parts)
87
- tuples = []
88
- for part in parts:
89
- match = cls._pattern_pre_release_part.fullmatch(part)
90
- assert match is not None
91
- pre_patch_match = match.group('prepatch')
92
- if pre_patch_match is not None:
93
- pre_patch = int(pre_patch_match)
94
- else:
95
- pre_patch = None
96
- tuples.append((match.group('preseparator'), match.group('pretype'), pre_patch))
97
- return tuples
98
-
99
- _re_local_separator = rf'({_re_sep})'
100
- _re_local_part = rf'({_re_alphanumeric})'
101
- _re_local_parts = rf'\+{_re_local_part}(?:{_re_local_separator}{_re_local_part})*'
102
- _pattern_local = re.compile(_re_local_parts)
103
-
104
- @classmethod
105
- def _parse_local_str(cls, local: str) -> tuple:
106
- match = cls._pattern_local.fullmatch(local)
107
- assert match is not None
108
- return tuple([_ for _ in match.groups() if _ is not None])
109
-
110
- _re_release = r'(?P<release>{n}(?:\.{n})?(?:\.{n})?)'.format(n=_re_number)
111
- _re_pre_release = r'(?P<prerelease>(?:(?:{0}{2})|(?:{0}?{1}{2}?))+)'.format(
112
- _re_sep, _re_letters, _re_number)
113
- _re_local = r'(?P<local>\+{0}([\.-]{0})*)'.format(_re_alphanumeric)
114
- # _re_named_parts_count = 3 + 3
115
- _re_version = rf'{_re_release}{_re_pre_release}?{_re_local}?'
116
- _pattern_version = re.compile(_re_version)
45
+ # pylint: disable = too-many-public-methods, too-many-instance-attributes
117
46
 
118
47
  @classmethod
119
48
  def from_str(cls, version_str: str):
120
49
  """Create version from string."""
121
- match = cls._pattern_version.fullmatch(version_str) # type: t.Optional[t.Match[str]]
50
+ match = patterns.VERSION.fullmatch(version_str) # type: t.Optional[t.Match[str]]
122
51
  if match is None:
123
52
  raise ValueError(f'version string {repr(version_str)} is invalid')
124
53
  _LOG.debug('version_query parsed version string %s into %s: %s %s',
@@ -128,9 +57,9 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
128
57
  _pre_release = match.group('prerelease')
129
58
  _local = match.group('local')
130
59
 
131
- major, minor, patch = cls._parse_release_str(_release)
132
- pre_release = None if _pre_release is None else cls._parse_pre_release_str(_pre_release)
133
- local = None if _local is None else cls._parse_local_str(_local)
60
+ major, minor, patch = parse_release_str(_release)
61
+ pre_release = None if _pre_release is None else parse_pre_release_str(_pre_release)
62
+ local = None if _local is None else parse_local_str(_local)
134
63
 
135
64
  return cls(major=major, minor=minor, patch=patch, pre_release=pre_release, local=local)
136
65
 
@@ -147,13 +76,13 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
147
76
  """Create version from a standard Python version object."""
148
77
  if not isinstance(py_version, packaging.version.Version):
149
78
  _LOG.warning('attempting to parse %s as packaging.version.Version...', type(py_version))
150
- ver = py_version._version
79
+ ver = py_version._version # pylint: disable = protected-access
151
80
  major, minor, patch = [ver.release[i] if len(ver.release) > i
152
81
  else None for i in range(3)]
153
82
  pre_release: t.Optional[t.Sequence[
154
83
  t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]]] = None
155
84
  local = None
156
- pre_ver: t.Optional[t.Tuple[None, int]]
85
+ pre_ver: t.Optional[t.Tuple[str | None, int]]
157
86
  if len(ver.release) == 4:
158
87
  pre_ver = (None, ver.release[3])
159
88
  elif len(ver.release) > 4:
@@ -176,12 +105,14 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
176
105
  local = tuple(itertools.chain.from_iterable(
177
106
  (dot, str(_)) for dot, _ in zip('.' * len(ver.local), ver.local)))[1:]
178
107
  _LOG.debug('parsing %s %s', type(py_version), py_version)
108
+ assert major is not None
179
109
  return cls(major, minor, patch, pre_release=pre_release, local=local)
180
110
 
181
111
  @classmethod
182
112
  def from_sem_version(cls, sem_version: t.Union[dict, semver.VersionInfo]):
183
113
  """Create version from semantic version object."""
184
114
  _LOG.debug('parsing %s %s', type(sem_version), sem_version)
115
+ local: str | tuple | None
185
116
  if isinstance(sem_version, semver.VersionInfo):
186
117
  major, minor, patch = sem_version.major, sem_version.minor, sem_version.patch
187
118
  pre_release = sem_version.prerelease
@@ -193,24 +124,24 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
193
124
  if pre_release is not None:
194
125
  raise NotImplementedError(sem_version)
195
126
  if local is not None:
196
- local = cls._parse_local_str(f'+{local}')
127
+ local = parse_local_str(f'+{local}')
197
128
  return cls(major, minor, patch, pre_release=pre_release, local=local)
198
129
 
199
130
  @classmethod
200
131
  def from_version(cls, version: 'Version'):
201
132
  return cls.from_dict(version.to_dict())
202
133
 
203
- def __init__(
134
+ def __init__( # pylint: disable = keyword-arg-before-vararg, too-many-arguments
204
135
  self, major: int, minor: t.Optional[int] = None, patch: t.Optional[int] = None, *args,
205
- pre_release: t.Sequence[
206
- t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]] = None,
207
- local: t.Union[str, tuple] = None):
136
+ pre_release: t.Optional[t.Sequence[
137
+ t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]]] = None,
138
+ local: t.Union[str, tuple, None] = None):
208
139
  self._major: t.Optional[int] = None
209
140
  self._minor: t.Optional[int] = None
210
141
  self._patch: t.Optional[int] = None
211
142
  self._pre_release: t.Optional[
212
143
  t.List[t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]]] = None
213
- self._local: t.Optional[t.Sequence[str]] = None
144
+ self._local: t.Optional[t.Tuple[str, ...]] = None
214
145
 
215
146
  self.release = major, minor, patch
216
147
 
@@ -224,7 +155,7 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
224
155
  args = args[consumed_args:]
225
156
  else:
226
157
  pre_release = None
227
- self.pre_release = pre_release
158
+ self.pre_release = pre_release if pre_release is None else list(pre_release)
228
159
 
229
160
  if args and local is not None:
230
161
  raise ValueError(f'args={args} and local={local} are present at the same time'
@@ -243,35 +174,51 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
243
174
 
244
175
  def _get_pre_release_from_args(self, args) -> t.Tuple[
245
176
  t.Sequence[t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]], int]:
177
+ """Get pre-release version component from args."""
178
+ if args and isinstance(args[0], tuple):
179
+ return self._get_prerelease_from_tuples(args)
180
+
246
181
  pre_release: t.List[t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]] = []
247
182
  consumed_args = 0
248
- if args and isinstance(args[0], tuple):
249
- for i, arg in enumerate(args):
250
- if not isinstance(arg, tuple):
251
- break
252
- if len(arg) == 3 and arg[0] in (None, '.', '-'):
253
- pre_release.append(arg)
254
- consumed_args += 1
255
- continue
256
- if i == len(args) - 1:
183
+ accumulated: t.List[t.Union[int, str, None]] = []
184
+ for i, arg in enumerate(args):
185
+ if not accumulated:
186
+ if arg in (None, '.', '-'):
187
+ if len(args) < i + 3:
188
+ raise ValueError(
189
+ 'expected 3 consecutive values'
190
+ f' from index {i} in args={args} in {repr(self)}')
191
+ else:
257
192
  break
258
- raise ValueError(f'pre-release segment arg={arg} (index {i} in args={args}'
259
- f' in {repr(self)}) must be a 3-tuple')
260
- else:
261
- accumulated: t.List[t.Union[int, str]] = []
262
- for i, arg in enumerate(args):
263
- if not accumulated:
264
- if arg in (None, '.', '-'):
265
- if len(args) < i + 3:
266
- raise ValueError(f'expected 3 consecutive values from index {i}'
267
- f' in args={args} in {repr(self)}')
268
- else:
269
- break
270
- accumulated.append(arg)
193
+ accumulated.append(arg)
194
+ consumed_args += 1
195
+ if len(accumulated) == 3:
196
+ _separator, _type, _patch = \
197
+ accumulated # pylint: disable = unbalanced-tuple-unpacking
198
+ assert _separator is None or isinstance(_separator, str), _separator
199
+ assert _type is None or isinstance(_type, str), _type
200
+ assert _patch is None or isinstance(_patch, int), _patch
201
+ pre_release.append((_separator, _type, _patch))
202
+ accumulated = []
203
+ return pre_release, consumed_args
204
+
205
+ def _get_prerelease_from_tuples(self, args) -> t.Tuple[
206
+ t.Sequence[t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]], int]:
207
+ """Get pre-release version component from args, assuming they are sequence of tuples."""
208
+ pre_release: t.List[t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]] = []
209
+ consumed_args = 0
210
+ for i, arg in enumerate(args):
211
+ if not isinstance(arg, tuple):
212
+ break
213
+ if len(arg) == 3 and arg[0] in (None, '.', '-'):
214
+ pre_release.append(arg)
271
215
  consumed_args += 1
272
- if len(accumulated) == 3:
273
- pre_release.append(tuple(accumulated))
274
- accumulated = []
216
+ continue
217
+ if i == len(args) - 1:
218
+ break
219
+ raise ValueError(
220
+ f'pre-release segment arg={arg} (index {i} in args={args} in {repr(self)})'
221
+ ' must be a 3-tuple')
275
222
  return pre_release, consumed_args
276
223
 
277
224
  @property
@@ -312,7 +259,7 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
312
259
 
313
260
  @property
314
261
  def pre_release(self) -> t.Optional[
315
- t.Sequence[t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]]]:
262
+ t.List[t.Tuple[t.Optional[str], t.Optional[str], t.Optional[int]]]]:
316
263
  """Pre-release version component."""
317
264
  if self._pre_release is None:
318
265
  return None
@@ -354,7 +301,7 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
354
301
  if pre_type is not None and not isinstance(pre_type, str):
355
302
  raise TypeError(
356
303
  f'pre_type={repr(pre_type)} is of wrong type {type(pre_type)} in {repr(self)}')
357
- if pre_type is not None and type(self)._pattern_letters.fullmatch(pre_type) is None:
304
+ if pre_type is not None and patterns.LETTERS.fullmatch(pre_type) is None:
358
305
  raise ValueError(f'pre_type={repr(pre_type)} has wrong value in {repr(self)}')
359
306
  if pre_patch is not None and not isinstance(pre_patch, int):
360
307
  raise TypeError(
@@ -393,7 +340,7 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
393
340
  raise TypeError(f'local_part or local_separator {repr(part)} is of wrong type'
394
341
  f' {type(part)} in {repr(self)}')
395
342
  if i % 2 == 0:
396
- if type(self)._pattern_alphanumeric.fullmatch(part) is None:
343
+ if patterns.ALPHANUMERIC.fullmatch(part) is None:
397
344
  raise ValueError(f'local_part={repr(part)} has wrong value in {repr(self)}')
398
345
  elif part not in ('-', '.'):
399
346
  raise ValueError(f'local_separator={repr(part)} has wrong value in {repr(self)}')
@@ -501,13 +448,13 @@ class Version(collections.abc.Hashable): # pylint: disable = too-many-public-me
501
448
  assert self._pre_release is not None
502
449
  version_tuple = self._pre_release[segment]
503
450
  if _version_tuple_checker(version_tuple, (True, True, False)):
504
- return '{}{}'.format(*version_tuple[:2])
451
+ return '{}{}'.format(*version_tuple[:2]) # pylint: disable = consider-using-f-string
505
452
  if _version_tuple_checker(version_tuple, (True, False, True)):
506
453
  return f'{version_tuple[0]}{version_tuple[2]}'
507
454
  if _version_tuple_checker(version_tuple, (False, True, True)):
508
- return '{}{}'.format(*version_tuple[1:])
455
+ return '{}{}'.format(*version_tuple[1:]) # pylint: disable = consider-using-f-string
509
456
  if _version_tuple_checker(version_tuple, (True, True, True)):
510
- return '{}{}{}'.format(*version_tuple)
457
+ return '{}{}{}'.format(*version_tuple) # pylint: disable = consider-using-f-string
511
458
  raise ValueError(f'cannot generate valid version string from {repr(self)}')
512
459
 
513
460
  def pre_release_to_str(self) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: version-query
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary: Zero-overhead package versioning for Python.
5
5
  Home-page: https://github.com/mbdevpl/version-query
6
6
  Download-URL:
@@ -81,7 +81,7 @@ Zero-overhead package versioning for Python.
81
81
  :alt: grade from Codacy
82
82
 
83
83
  .. image:: https://img.shields.io/github/license/mbdevpl/version-query.svg
84
- :target: https://github.com/mbdevpl/version-query/blob/v1.6.1/NOTICE
84
+ :target: https://github.com/mbdevpl/version-query/blob/v1.6.2/NOTICE
85
85
  :alt: license
86
86
 
87
87
  Package versioning toolkit for Python which relies on git tags, git history traversal
@@ -361,9 +361,9 @@ Incrementing any version component clears all existing following components.
361
361
  Examples of how version is incremented:
362
362
 
363
363
  * for ``1.5``, incrementing ``<major>`` results in ``2.0``;
364
- * for ``1.5.1-2.4``, ``<minor>``++ results in ``1.6``;
365
- * ``1.5.1-2.4``, ``<patch>``++, ``1.5.2``;
366
- * ``1.5.1``, ``<major>``+=3, ``4.0.0``.
364
+ * for ``1.5.1-2.4``, ``<minor>`` ++ results in ``1.6``;
365
+ * ``1.5.1-2.4``, ``<patch>`` ++, ``1.5.2``;
366
+ * ``1.5.1``, ``<major>`` += 3, ``4.0.0``.
367
367
 
368
368
 
369
369
  API details
@@ -491,8 +491,8 @@ Requirements
491
491
 
492
492
  Python version 3.9 or later.
493
493
 
494
- Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.6.1/requirements.txt>`_.
494
+ Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.6.2/requirements.txt>`_.
495
495
 
496
- Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.6.1/requirements_test.txt>`_.
496
+ Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.6.2/requirements_test.txt>`_.
497
497
 
498
498
  Tested on Linux, macOS and Windows.
@@ -17,6 +17,8 @@ setup.py
17
17
  ./version_query/_version.py
18
18
  ./version_query/git_query.py
19
19
  ./version_query/main.py
20
+ ./version_query/parser.py
21
+ ./version_query/patterns.py
20
22
  ./version_query/py.typed
21
23
  ./version_query/py_query.py
22
24
  ./version_query/query.py
@@ -32,6 +34,8 @@ version_query/__main__.py
32
34
  version_query/_version.py
33
35
  version_query/git_query.py
34
36
  version_query/main.py
37
+ version_query/parser.py
38
+ version_query/patterns.py
35
39
  version_query/py.typed
36
40
  version_query/py_query.py
37
41
  version_query/query.py
File without changes
File without changes
File without changes
File without changes
File without changes