/usr/lib/python3/dist-packages/releases/models.py is in python3-releases 1.4.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | from functools import reduce
from operator import xor
from docutils import nodes
from semantic_version import Version as StrictVersion, Spec
import six
class Version(StrictVersion):
"""
Version subclass toggling ``partial=True`` by default.
"""
def __init__(self, version_string, partial=True):
super(Version, self).__init__(version_string, partial)
# Issue type list (keys) + color values
ISSUE_TYPES = {
'bug': 'A04040',
'feature': '40A056',
'support': '4070A0',
}
class Issue(nodes.Element):
# Technically, we just need number, but heck, you never know...
_cmp_keys = ('type', 'number', 'backported', 'major')
@property
def type(self):
return self['type_']
@property
def is_featurelike(self):
if self.type == 'bug':
return self.major
else:
return not self.backported
@property
def is_buglike(self):
return not self.is_featurelike
@property
def backported(self):
return self.get('backported', False)
@property
def major(self):
return self.get('major', False)
@property
def number(self):
return self.get('number', None)
@property
def spec(self):
return self.get('spec', None)
def __eq__(self, other):
for attr in self._cmp_keys:
if getattr(self, attr, None) != getattr(other, attr, None):
return False
return True
def __hash__(self):
return reduce(xor, [hash(getattr(self, x)) for x in self._cmp_keys])
def minor_releases(self, manager):
"""
Return all minor release line labels found in ``manager``.
"""
# TODO: yea deffo need a real object for 'manager', heh. E.g. we do a
# very similar test for "do you have any actual releases yet?"
# elsewhere. (This may be fodder for changing how we roll up
# pre-major-release features though...?)
return [
key for key, value in six.iteritems(manager)
if any(x for x in value if not x.startswith('unreleased'))
]
def default_spec(self, manager):
"""
Given the current release-lines structure, return a default Spec.
Specifics:
* For feature-like issues, only the highest major release is used, so
given a ``manager`` with top level keys of ``[1, 2]``, this would
return ``Spec(">=2")``.
* When ``releases_always_forwardport_features`` is ``True``, that
behavior is nullified, and this function always returns the empty
``Spec`` (which matches any and all versions/lines).
* For bugfix-like issues, we only consider major release families which
have actual releases already.
* Thus the core difference here is that features are 'consumed' by
upcoming major releases, and bugfixes are not.
* When the ``unstable_prehistory`` setting is ``True``, the default
spec starts at the oldest non-zero release line. (Otherwise, issues
posted after prehistory ends would try being added to the 0.x part of
the tree, which makes no sense in unstable-prehistory mode.)
"""
# TODO: I feel like this + the surrounding bits in add_to_manager()
# could be consolidated & simplified...
specstr = ""
# Make sure truly-default spec skips 0.x if prehistory was unstable.
stable_families = manager.stable_families
if manager.config.releases_unstable_prehistory and stable_families:
specstr = ">={}".format(min(stable_families))
if self.is_featurelike:
# TODO: if app->config-><releases_always_forwardport_features or
# w/e
if True:
specstr = ">={}".format(max(manager.keys()))
else:
# Can only meaningfully limit to minor release buckets if they
# actually exist yet.
buckets = self.minor_releases(manager)
if buckets:
specstr = ">={}".format(max(buckets))
return Spec(specstr) if specstr else Spec()
def add_to_manager(self, manager):
"""
Given a 'manager' structure, add self to one or more of its 'buckets'.
"""
# Derive version spec allowing us to filter against major/minor buckets
spec = self.spec or self.default_spec(manager)
# Only look in appropriate major version/family; if self is an issue
# declared as living in e.g. >=2, this means we don't even bother
# looking in the 1.x family.
families = [Version(str(x)) for x in manager]
versions = list(spec.filter(families))
for version in versions:
family = version.major
# Within each family, we further limit which bugfix lines match up
# to what self cares about (ignoring 'unreleased' until later)
candidates = [
Version(x)
for x in manager[family]
if not x.startswith('unreleased')
]
# Select matching release lines (& stringify)
buckets = []
bugfix_buckets = [str(x) for x in spec.filter(candidates)]
# Add back in unreleased_* as appropriate
# TODO: probably leverage Issue subclasses for this eventually?
if self.is_buglike:
buckets.extend(bugfix_buckets)
# Don't put into JUST unreleased_bugfix; it implies that this
# major release/family hasn't actually seen any releases yet
# and only exists for features to go into.
if bugfix_buckets:
buckets.append('unreleased_bugfix')
# Obtain list of minor releases to check for "haven't had ANY
# releases yet" corner case, in which case ALL issues get thrown in
# unreleased_feature for the first release to consume.
# NOTE: assumes first release is a minor or major one,
# but...really? why would your first release be a bugfix one??
no_releases = not self.minor_releases(manager)
if self.is_featurelike or self.backported or no_releases:
buckets.append('unreleased_feature')
# Now that we know which buckets are appropriate, add ourself to
# all of them. TODO: or just...do it above...instead...
for bucket in buckets:
manager[family][bucket].append(self)
def __repr__(self):
flag = ''
if self.backported:
flag = 'backported'
elif self.major:
flag = 'major'
elif self.spec:
flag = self.spec
if flag:
flag = ' ({})'.format(flag)
return '<{issue.type} #{issue.number}{flag}>'.format(issue=self,
flag=flag)
class Release(nodes.Element):
@property
def number(self):
return self['number']
@property
def minor(self):
# TODO: use Version
return '.'.join(self.number.split('.')[:-1])
@property
def family(self):
# TODO: use Version.major
# TODO: and probs just rename to .major, 'family' is dumb tbh
return int(self.number.split('.')[0])
def __repr__(self):
return '<release {}>'.format(self.number)
|