Source code for apt_smart.releases

# Easy to use metadata on Debian and Ubuntu releases.
#
# Author: martin68 and Peter Odding
# Last Change: May 31, 2020
# URL: https://apt-smart.readthedocs.io

"""
Easy to use metadata on Debian and Ubuntu releases.

This module started out with the purpose of reliable `end of life`_ (EOL)
detection for Debian and Ubuntu releases based on data provided by the
distro-info-data_  package. Since then the need arose to access more of the
available metadata and so the ``eol`` module became the ``releases`` module.

Debian and Ubuntu releases have an EOL date that marks the end of support for
each release. At that date the release stops receiving further (security)
updates and some time after package mirrors stop serving the release.

The distro-info-data_ package contains CSV files with metadata about Debian and
Ubuntu releases. This module parses those CSV files to make this metadata
available in Python. This enables `apt-smart` to make an informed
decision about the following questions:

1. Is a given Debian or Ubuntu release expected to be available on mirrors or
   will it only be available in the archive of old releases?

2. Is the signing key of a given Ubuntu release expected to be included in the
   main keyring (:data:`UBUNTU_KEYRING_CURRENT`) or should the keyring with
   removed keys (:data:`UBUNTU_KEYRING_REMOVED`) be used?

To make it possible to run `apt-smart` without direct access to the
CSV files, a copy of the relevant information has been embedded in the source
code.

.. _end of life: https://en.wikipedia.org/wiki/End-of-life_(product)
.. _distro-info-data: https://packages.debian.org/distro-info-data
"""

# Standard library modules.
import csv
import datetime
import decimal
import glob
import logging
import numbers
import os

# External dependencies.
import six
from executor import execute
from humanfriendly.decorators import cached
try:
    from property_manager3 import PropertyManager, key_property, lazy_property, required_property, writable_property
except ImportError:
    from property_manager import PropertyManager, key_property, lazy_property, required_property, writable_property
from six import string_types
from itertools import product


DISTRO_INFO_DIRECTORY = '/usr/share/distro-info'
"""The pathname of the directory with CSV files containing release metadata (a string)."""

DEBIAN_KEYRING_CURRENT = '/usr/share/keyrings/debian-archive-keyring.gpg'
"""The pathname of the main Debian keyring file (a string)."""

UBUNTU_KEYRING_CURRENT = '/usr/share/keyrings/ubuntu-archive-keyring.gpg'
"""The pathname of the main Ubuntu keyring file (a string)."""

UBUNTU_KEYRING_REMOVED = '/usr/share/keyrings/ubuntu-archive-removed-keys.gpg'
"""The pathname of the Ubuntu keyring file with removed keys (a string)."""

# Public identifiers that require documentation.
__all__ = (
    'DISTRO_INFO_DIRECTORY',
    'DEBIAN_KEYRING_CURRENT',
    'UBUNTU_KEYRING_CURRENT',
    'UBUNTU_KEYRING_REMOVED',
    'Release',
    'coerce_release',
    'discover_releases',
    'ubuntu_keyring_updated',
)

# Initialize a logger.
logger = logging.getLogger(__name__)


[docs]def coerce_release(value): """ Try to coerce the given value to a Debian or Ubuntu release. :param value: The value to coerce (a number, a string or a :class:`Release` object). :returns: A :class:`Release` object. :raises: :exc:`~exceptions.ValueError` when the given value cannot be coerced to a known release. The following values can be coerced: - Numbers and numbers formatted as strings match :attr:`Release.version`. - Strings match :attr:`Release.codename` (case insensitive). .. warning:: Don't use floating point numbers like 10.04 because their actual value will be something like 10.039999999999999147 which won't match the intended release. """ # Release objects pass through untouched. if isinstance(value, Release): return value # Numbers and version strings are matched against release versions. if isinstance(value, numbers.Number) or is_version_string(value): typed_value = decimal.Decimal(value) matches = [release for release in discover_releases() if release.version == typed_value] if len(matches) != 1: msg = "The number %s doesn't match a known Debian or Ubuntu or Linux Mint release!" raise ValueError(msg % value) return matches[0] # Other strings are matched against release code names. matches = [release for release in discover_releases() if value.lower() in release.codename.lower()] if len(matches) != 1: msg = "The string %r doesn't match a known Debian or Ubuntu or Linux Mint release!" raise ValueError(msg % value) return matches[0]
[docs]@cached def discover_releases(): """ Discover known Debian and Ubuntu releases. :returns: A list of discovered :class:`Release` objects sorted by :attr:`~Release.distributor_id` and :attr:`~Release.version`. The first time this function is called it will try to parse the CSV files in ``/usr/share/distro-info`` and merge any releases it finds with the releases embedded into the source code of this module. The result is cached and returned each time the function is called. It's not a problem if the ``/usr/share/distro-info`` directory doesn't exist or doesn't contain any ``*.csv`` files (it won't cause a warning or error). Of course in this case only the embedded releases will be returned. """ # Discover the known releases on the first call to discover_releases(). # First we check the CSV files on the system where apt-smart # is running, because those files may be more up-to-date than the # bundled information is. result = set() for filename in glob.glob(os.path.join(DISTRO_INFO_DIRECTORY, '*.csv')): for release in parse_csv_file(filename): result.add(release) # Add the releases bundled with apt-smart to the result # without causing duplicate entries (due to the use of a set and key # properties). result.update(BUNDLED_RELEASES) # Sort the releases by distributor ID and version / series. return sorted(result, key=lambda r: (r.distributor_id, r.version or 0, r.series))
def table_to_2d(table_tag): # https://stackoverflow.com/a/48451104 rowspans = [] # track pending rowspans rows = table_tag.find_all('tr') # first scan, see how many columns we need colcount = 0 for r, row in enumerate(rows): cells = row.find_all(['td', 'th'], recursive=False) # count columns (including spanned). # add active rowspans from preceding rows # we *ignore* the colspan value on the last cell, to prevent # creating 'phantom' columns with no actual cells, only extended # colspans. This is achieved by hardcoding the last cell width as 1. # a colspan of 0 means "fill until the end" but can really only apply # to the last cell; ignore it elsewhere. colcount = max( colcount, sum(int(c.get('colspan', 1)) or 1 for c in cells[:-1]) + len(cells[-1:]) + len(rowspans)) # update rowspan bookkeeping; 0 is a span to the bottom. rowspans += [int(c.get('rowspan', 1)) or len(rows) - r for c in cells] rowspans = [s - 1 for s in rowspans if s > 1] # it doesn't matter if there are still rowspan numbers 'active'; no extra # rows to show in the table means the larger than 1 rowspan numbers in the # last table row are ignored. # build an empty matrix for all possible cells table = [[None] * colcount for row in rows] # fill matrix from row data rowspans = {} # track pending rowspans, column number mapping to count for row, row_elem in enumerate(rows): span_offset = 0 # how many columns are skipped due to row and colspans for col, cell in enumerate(row_elem.find_all(['td', 'th'], recursive=False)): # adjust for preceding row and colspans col += span_offset while rowspans.get(col, 0): span_offset += 1 col += 1 # fill table data rowspan = rowspans[col] = int(cell.get('rowspan', 1)) or len(rows) - row colspan = int(cell.get('colspan', 1)) or colcount - col # next column is offset by the colspan span_offset += colspan - 1 value = cell.get_text() for drow, dcol in product(range(rowspan), range(colspan)): try: table[row + drow][col + dcol] = value rowspans[col + dcol] = rowspan except IndexError: # rowspan or colspan outside the confines of the table pass # update rowspan bookkeeping rowspans = {c: s - 1 for c, s in rowspans.items() if s > 1} return table def discover_linuxmint_releases(array_2d): d = {} # a dict to map table head to column number head = array_2d[0] for i, data in enumerate(head): d[data] = i last = None for entry in array_2d[1:-1]: if entry[d['Codename\n']] == last: continue # skip same codename entry last = entry[d['Codename\n']] yield Release( codename=parse_data_wiki(entry[d['Codename\n']]), compatible_repository=( parse_data_wiki(entry[d['Compatible repository\n']]).split('(')[1].split(' ')[0].lower() if entry[d['Compatible repository\n']].find('(') > 0 else parse_data_wiki(entry[d['Compatible repository\n']]) ), is_lts=('Yes' in entry[d['LTS?\n']]), created_date=parse_date_wiki(entry[d['Release date\n']]), distributor_id='linuxmint', eol_date=parse_date_wiki(entry[d['Support End\n']]), extended_eol_date=parse_date_wiki(entry[d['Support End\n']]), release_date=parse_date_wiki(entry[d['Release date\n']]), series=parse_data_wiki(entry[d['Codename\n']]).lower(), version=parse_version_wiki(entry[d['Version\n']]) if entry[d['Version\n']] else None, ) def is_version_string(value): """Check whether the given value is a string containing a positive number.""" try: return isinstance(value, string_types) and float(value) > 0 except Exception: return False def parse_csv_file(filename): """ Parse a CSV file in the format of the ``/usr/share/distro-info/*.csv`` files. :param filename: The pathname of the CSV file (a string). :returns: A generator of :class:`Release` objects. """ # We import this here to avoid a circular import. from apt_smart.backends.debian import LTS_RELEASES basename, extension = os.path.splitext(os.path.basename(filename)) distributor_id = basename.lower() with open(filename) as handle: for entry in csv.DictReader(handle): yield Release( codename=entry['codename'], is_lts=( entry['series'] in LTS_RELEASES if distributor_id == 'debian' else ( 'LTS' in entry['version'] if distributor_id == 'ubuntu' else ( # Neither Debian nor Ubuntu, let's not assume anything... False ) ) ), created_date=parse_date(entry['created']), distributor_id=distributor_id, eol_date=parse_date(entry['eol']), extended_eol_date=( # Special handling for Debian LTS releases. datetime.datetime.fromtimestamp(LTS_RELEASES[entry['series']]).date() if distributor_id == 'debian' and entry['series'] in LTS_RELEASES # Ubuntu LTS releases are defined by the CSV file. else parse_date(entry.get('eol-server')) ), release_date=parse_date(entry['release']), series=entry['series'], version=parse_version(entry['version']) if entry['version'] else None, ) def parse_data_wiki(value): r"""Strip a string such as ``19 December 2018 [18]\n`` to ``19 December 2018``""" if six.PY2: return value.encode("utf8").split('[')[0].strip() else: return value.split('[')[0].strip() def parse_date(value): """Convert a ``YYYY-MM-DD`` string to a :class:`datetime.date` object.""" return datetime.datetime.strptime(value, '%Y-%m-%d').date() if value else None def parse_date_wiki(value): r"""Convert a string such as ``19 December 2018`` ``August 02, 2019\n`` to a :class:`datetime.date` object.""" value = parse_data_wiki(value) if value == 'Unknown': value = '30 April 2008' if len(value) < 15: if not value[:1].isdigit(): # Only Month Year value = '30 ' + value if len(value) < 5: # Only Year value = '30 April ' + value try: return datetime.datetime.strptime(value, '%d %B %Y').date() if value else None except ValueError: return datetime.datetime.strptime(value, '%B %d, %Y').date() if value else None def parse_version(value): """Convert a version string to a floating point number.""" for token in value.split(): try: return decimal.Decimal(token) except ValueError: pass msg = "Failed to convert version string to number! (%r)" raise ValueError(msg % value) def parse_version_wiki(value): """Convert a version string (got from wiki page) to a floating point number.""" value = parse_data_wiki(value) value = value.split(": ")[1] for token in value.split(): try: return decimal.Decimal(token) except ValueError: pass msg = "Failed to convert version string to number! (%r)" raise ValueError(msg % value)
[docs]@cached def ubuntu_keyring_updated(): """ Detect update `#1363482`_ to the ``ubuntu-keyring`` package. :returns: :data:`True` when version ``2016.10.27`` or newer is installed, :data:`False` when an older version is installed. This function checks if the changes discussed in Launchpad bug `#1363482`_ apply to the current system using the ``dpkg-query --show`` and ``dpkg --compare-versions`` commands. For more details refer to `issue #8`_. .. _#1363482: https://bugs.launchpad.net/ubuntu/+source/ubuntu-keyring/+bug/1363482 .. _issue #8: https://github.com/xolox/python-apt-mirror-updater/issues/8 """ # Use external commands to check the installed version of the package. version = execute('dpkg-query', '--show', '--showformat=${Version}', 'ubuntu-keyring', capture=True) logger.debug("Detected ubuntu-keyring package version: %s", version) updated = execute('dpkg', '--compare-versions', version, '>=', '2016.10.27', check=False, silent=True) logger.debug("Does Launchpad bug #1363482 apply? %s", updated) return updated
[docs]class Release(PropertyManager): """Data class for metadata on Debian and Ubuntu releases."""
[docs] @key_property def codename(self): """The long version of :attr:`series` (a string)."""
[docs] @writable_property def compatible_repository(self): """For Linux Mint, compatible which Ubuntu version's repository"""
[docs] @required_property def created_date(self): """The date on which the release was created (a :class:`~datetime.date` object)."""
[docs] @key_property def distributor_id(self): """The name of the distributor (a string like ``debian`` or ``ubuntu`` or ``linuxmint``)."""
[docs] @writable_property def eol_date(self): """The date on which the desktop release stops being supported (a :class:`~datetime.date` object)."""
[docs] @writable_property def extended_eol_date(self): """The date on which the server release stops being supported (a :class:`~datetime.date` object)."""
[docs] @lazy_property def is_eol(self): """Whether the release has reached its end-of-life date (a boolean or :data:`None`).""" eol_date = self.extended_eol_date or self.eol_date if eol_date: return datetime.date.today() >= eol_date else: return False
[docs] @writable_property def is_lts(self): """Whether a release is a long term support release (a boolean)."""
[docs] @writable_property def release_date(self): """The date on which the release was published (a :class:`~datetime.date` object)."""
[docs] @key_property def series(self): """The short version of :attr:`codename` (a string)."""
[docs] @writable_property def version(self): """ The version number of the release (a :class:`~decimal.Decimal` number). This property has a :class:`~decimal.Decimal` value to enable proper sorting based on numeric comparison. """
[docs] @lazy_property def keyring_file(self): """ The pathname of the keyring with signing keys for this release (a string). This property exists to work around a bug in ``debootstrap`` which may use the wrong keyring to create Ubuntu chroots, for more details refer to :func:`ubuntu_keyring_updated()`. """ filename = None reason = None logger.debug("Selecting keyring file for %s ..", self) if self.distributor_id == 'debian': filename = DEBIAN_KEYRING_CURRENT reason = "only known keyring" elif self.distributor_id == 'ubuntu': if ubuntu_keyring_updated(): if self.version > decimal.Decimal('12.04'): filename = UBUNTU_KEYRING_CURRENT reason = "new keyring package / new release" else: filename = UBUNTU_KEYRING_REMOVED reason = "new keyring package / old release" else: filename = UBUNTU_KEYRING_CURRENT reason = "old keyring package" else: msg = "Unsupported distributor ID! (%s)" raise EnvironmentError(msg % self.distributor_id) logger.debug("Using %s (reason: %s).", filename, reason) return filename
[docs] def __str__(self): """ Render a human friendly representation of a :class:`Release` object. The result will be something like this: - Debian 9 (stretch) - Ubuntu 18.04 (bionic) """ label = [self.distributor_id.capitalize()] if self.version: label.append(str(self.version)) label.append("(%s)" % self.series) return " ".join(label)
# [[[cog # # import cog # import decimal # from bs4 import BeautifulSoup # from apt_smart.releases import discover_releases, discover_linuxmint_releases, table_to_2d # from apt_smart.http import fetch_url # # indent = " " * 4 # cog.out("\nBUNDLED_RELEASES = [\n") # for release in discover_releases(): # cog.out(indent + "Release(\n") # for name in release.find_properties(cached=False): # value = getattr(release, name) # if value is not None: # if isinstance(value, decimal.Decimal): # # It seems weirdly inconsistency to me that this is needed # # for decimal.Decimal() but not for datetime.date() but I # # guess the simple explanation is that repr() output simply # # isn't guaranteed to be accepted by eval(). # value = "decimal." + repr(value) # else: # value = repr(value) # cog.out(indent * 2 + name + "=" + value + ",\n") # cog.out(indent + "),\n") # url = 'https://en.wikipedia.org/wiki/Linux_Mint_version_history' # response = fetch_url(url, timeout=15, retry=True) # soup = BeautifulSoup(response, 'html.parser') # tables = soup.findAll('table') # for release in discover_linuxmint_releases(table_to_2d(tables[1])): # cog.out(indent + "Release(\n") # for name in release.find_properties(cached=False): # value = getattr(release, name) # if value is not None: # if isinstance(value, decimal.Decimal): # # It seems weirdly inconsistency to me that this is needed # # for decimal.Decimal() but not for datetime.date() but I # # guess the simple explanation is that repr() output simply # # isn't guaranteed to be accepted by eval(). # value = "decimal." + repr(value) # else: # value = repr(value) # cog.out(indent * 2 + name + "=" + value + ",\n") # cog.out(indent + "),\n") # cog.out("]\n\n") # # ]]] BUNDLED_RELEASES = [ Release( codename='Experimental', created_date=datetime.date(1993, 8, 16), distributor_id='debian', is_lts=False, series='experimental', ), Release( codename='Sid', created_date=datetime.date(1993, 8, 16), distributor_id='debian', is_lts=False, series='sid', ), Release( codename='Buzz', created_date=datetime.date(1993, 8, 16), distributor_id='debian', eol_date=datetime.date(1997, 6, 5), is_lts=False, release_date=datetime.date(1996, 6, 17), series='buzz', version=decimal.Decimal('1.1'), ), Release( codename='Rex', created_date=datetime.date(1996, 6, 17), distributor_id='debian', eol_date=datetime.date(1998, 6, 5), is_lts=False, release_date=datetime.date(1996, 12, 12), series='rex', version=decimal.Decimal('1.2'), ), Release( codename='Bo', created_date=datetime.date(1996, 12, 12), distributor_id='debian', eol_date=datetime.date(1999, 3, 9), is_lts=False, release_date=datetime.date(1997, 6, 5), series='bo', version=decimal.Decimal('1.3'), ), Release( codename='Hamm', created_date=datetime.date(1997, 6, 5), distributor_id='debian', eol_date=datetime.date(2000, 3, 9), is_lts=False, release_date=datetime.date(1998, 7, 24), series='hamm', version=decimal.Decimal('2.0'), ), Release( codename='Slink', created_date=datetime.date(1998, 7, 24), distributor_id='debian', eol_date=datetime.date(2000, 10, 30), is_lts=False, release_date=datetime.date(1999, 3, 9), series='slink', version=decimal.Decimal('2.1'), ), Release( codename='Potato', created_date=datetime.date(1999, 3, 9), distributor_id='debian', eol_date=datetime.date(2003, 7, 30), is_lts=False, release_date=datetime.date(2000, 8, 15), series='potato', version=decimal.Decimal('2.2'), ), Release( codename='Woody', created_date=datetime.date(2000, 8, 15), distributor_id='debian', eol_date=datetime.date(2006, 6, 30), is_lts=False, release_date=datetime.date(2002, 7, 19), series='woody', version=decimal.Decimal('3.0'), ), Release( codename='Sarge', created_date=datetime.date(2002, 7, 19), distributor_id='debian', eol_date=datetime.date(2008, 3, 30), is_lts=False, release_date=datetime.date(2005, 6, 6), series='sarge', version=decimal.Decimal('3.1'), ), Release( codename='Etch', created_date=datetime.date(2005, 6, 6), distributor_id='debian', eol_date=datetime.date(2010, 2, 15), is_lts=False, release_date=datetime.date(2007, 4, 8), series='etch', version=decimal.Decimal('4.0'), ), Release( codename='Lenny', created_date=datetime.date(2007, 4, 8), distributor_id='debian', eol_date=datetime.date(2012, 2, 6), is_lts=False, release_date=datetime.date(2009, 2, 14), series='lenny', version=decimal.Decimal('5.0'), ), Release( codename='Squeeze', created_date=datetime.date(2009, 2, 14), distributor_id='debian', eol_date=datetime.date(2014, 5, 31), is_lts=False, release_date=datetime.date(2011, 2, 6), series='squeeze', version=decimal.Decimal('6.0'), ), Release( codename='Wheezy', created_date=datetime.date(2011, 2, 6), distributor_id='debian', eol_date=datetime.date(2016, 4, 26), is_lts=False, release_date=datetime.date(2013, 5, 4), series='wheezy', version=decimal.Decimal('7'), ), Release( codename='Jessie', created_date=datetime.date(2013, 5, 4), distributor_id='debian', eol_date=datetime.date(2018, 6, 6), extended_eol_date=datetime.date(2020, 6, 30), is_lts=True, release_date=datetime.date(2015, 4, 25), series='jessie', version=decimal.Decimal('8'), ), Release( codename='Stretch', created_date=datetime.date(2015, 4, 25), distributor_id='debian', eol_date=datetime.date(2020, 7, 6), extended_eol_date=datetime.date(2022, 6, 30), is_lts=True, release_date=datetime.date(2017, 6, 17), series='stretch', version=decimal.Decimal('9'), ), Release( codename='Buster', created_date=datetime.date(2017, 6, 17), distributor_id='debian', is_lts=False, release_date=datetime.date(2019, 7, 6), series='buster', version=decimal.Decimal('10'), ), Release( codename='Bullseye', created_date=datetime.date(2019, 7, 6), distributor_id='debian', is_lts=False, series='bullseye', version=decimal.Decimal('11'), ), Release( codename='Bookworm', created_date=datetime.date(2021, 8, 1), distributor_id='debian', is_lts=False, series='bookworm', version=decimal.Decimal('12'), ), Release( codename='Ada', compatible_repository='Kubuntu 6.06', created_date=datetime.date(2006, 8, 27), distributor_id='linuxmint', eol_date=datetime.date(2008, 4, 30), extended_eol_date=datetime.date(2008, 4, 30), is_lts=False, release_date=datetime.date(2006, 8, 27), series='ada', version=decimal.Decimal('1.0'), ), Release( codename='Barbara', compatible_repository='edgy', created_date=datetime.date(2006, 11, 13), distributor_id='linuxmint', eol_date=datetime.date(2008, 4, 30), extended_eol_date=datetime.date(2008, 4, 30), is_lts=False, release_date=datetime.date(2006, 11, 13), series='barbara', version=decimal.Decimal('2.0'), ), Release( codename='Bea', compatible_repository='edgy', created_date=datetime.date(2006, 12, 20), distributor_id='linuxmint', eol_date=datetime.date(2008, 4, 30), extended_eol_date=datetime.date(2008, 4, 30), is_lts=False, release_date=datetime.date(2006, 12, 20), series='bea', version=decimal.Decimal('2.1'), ), Release( codename='Bianca', compatible_repository='edgy', created_date=datetime.date(2007, 2, 20), distributor_id='linuxmint', eol_date=datetime.date(2008, 4, 30), extended_eol_date=datetime.date(2008, 4, 30), is_lts=False, release_date=datetime.date(2007, 2, 20), series='bianca', version=decimal.Decimal('2.2'), ), Release( codename='Cassandra', compatible_repository='feisty', created_date=datetime.date(2007, 5, 30), distributor_id='linuxmint', eol_date=datetime.date(2008, 10, 30), extended_eol_date=datetime.date(2008, 10, 30), is_lts=False, release_date=datetime.date(2007, 5, 30), series='cassandra', version=decimal.Decimal('3.0'), ), Release( codename='Celena', compatible_repository='feisty', created_date=datetime.date(2007, 9, 24), distributor_id='linuxmint', eol_date=datetime.date(2008, 10, 30), extended_eol_date=datetime.date(2008, 10, 30), is_lts=False, release_date=datetime.date(2007, 9, 24), series='celena', version=decimal.Decimal('3.1'), ), Release( codename='Daryna', compatible_repository='gutsy', created_date=datetime.date(2007, 10, 15), distributor_id='linuxmint', eol_date=datetime.date(2009, 4, 30), extended_eol_date=datetime.date(2009, 4, 30), is_lts=False, release_date=datetime.date(2007, 10, 15), series='daryna', version=decimal.Decimal('4.0'), ), Release( codename='Elyssa', compatible_repository='hardy', created_date=datetime.date(2008, 6, 8), distributor_id='linuxmint', eol_date=datetime.date(2011, 4, 30), extended_eol_date=datetime.date(2011, 4, 30), is_lts=True, release_date=datetime.date(2008, 6, 8), series='elyssa', version=decimal.Decimal('5'), ), Release( codename='Felicia', compatible_repository='intrepid', created_date=datetime.date(2008, 12, 15), distributor_id='linuxmint', eol_date=datetime.date(2010, 4, 30), extended_eol_date=datetime.date(2010, 4, 30), is_lts=False, release_date=datetime.date(2008, 12, 15), series='felicia', version=decimal.Decimal('6'), ), Release( codename='Gloria', compatible_repository='jaunty', created_date=datetime.date(2009, 5, 26), distributor_id='linuxmint', eol_date=datetime.date(2010, 10, 30), extended_eol_date=datetime.date(2010, 10, 30), is_lts=False, release_date=datetime.date(2009, 5, 26), series='gloria', version=decimal.Decimal('7'), ), Release( codename='Helena', compatible_repository='karmic', created_date=datetime.date(2009, 11, 28), distributor_id='linuxmint', eol_date=datetime.date(2011, 4, 30), extended_eol_date=datetime.date(2011, 4, 30), is_lts=False, release_date=datetime.date(2009, 11, 28), series='helena', version=decimal.Decimal('8'), ), Release( codename='Isadora', compatible_repository='lucid', created_date=datetime.date(2010, 5, 18), distributor_id='linuxmint', eol_date=datetime.date(2013, 4, 30), extended_eol_date=datetime.date(2013, 4, 30), is_lts=True, release_date=datetime.date(2010, 5, 18), series='isadora', version=decimal.Decimal('9'), ), Release( codename='Julia', compatible_repository='maverick', created_date=datetime.date(2010, 11, 12), distributor_id='linuxmint', eol_date=datetime.date(2012, 4, 30), extended_eol_date=datetime.date(2012, 4, 30), is_lts=False, release_date=datetime.date(2010, 11, 12), series='julia', version=decimal.Decimal('10'), ), Release( codename='Katya', compatible_repository='natty', created_date=datetime.date(2011, 5, 26), distributor_id='linuxmint', eol_date=datetime.date(2012, 10, 30), extended_eol_date=datetime.date(2012, 10, 30), is_lts=False, release_date=datetime.date(2011, 5, 26), series='katya', version=decimal.Decimal('11'), ), Release( codename='Lisa', compatible_repository='oneiric', created_date=datetime.date(2011, 11, 26), distributor_id='linuxmint', eol_date=datetime.date(2013, 4, 30), extended_eol_date=datetime.date(2013, 4, 30), is_lts=False, release_date=datetime.date(2011, 11, 26), series='lisa', version=decimal.Decimal('12'), ), Release( codename='Maya', compatible_repository='precise', created_date=datetime.date(2012, 5, 23), distributor_id='linuxmint', eol_date=datetime.date(2017, 4, 30), extended_eol_date=datetime.date(2017, 4, 30), is_lts=True, release_date=datetime.date(2012, 5, 23), series='maya', version=decimal.Decimal('13'), ), Release( codename='Nadia', compatible_repository='quantal', created_date=datetime.date(2012, 11, 20), distributor_id='linuxmint', eol_date=datetime.date(2014, 5, 30), extended_eol_date=datetime.date(2014, 5, 30), is_lts=False, release_date=datetime.date(2012, 11, 20), series='nadia', version=decimal.Decimal('14'), ), Release( codename='Olivia', compatible_repository='raring', created_date=datetime.date(2013, 5, 29), distributor_id='linuxmint', eol_date=datetime.date(2014, 1, 30), extended_eol_date=datetime.date(2014, 1, 30), is_lts=False, release_date=datetime.date(2013, 5, 29), series='olivia', version=decimal.Decimal('15'), ), Release( codename='Petra', compatible_repository='saucy', created_date=datetime.date(2013, 11, 30), distributor_id='linuxmint', eol_date=datetime.date(2014, 7, 30), extended_eol_date=datetime.date(2014, 7, 30), is_lts=False, release_date=datetime.date(2013, 11, 30), series='petra', version=decimal.Decimal('16'), ), Release( codename='Qiana', compatible_repository='trusty', created_date=datetime.date(2014, 5, 31), distributor_id='linuxmint', eol_date=datetime.date(2019, 4, 30), extended_eol_date=datetime.date(2019, 4, 30), is_lts=True, release_date=datetime.date(2014, 5, 31), series='qiana', version=decimal.Decimal('17'), ), Release( codename='Rebecca', compatible_repository='trusty', created_date=datetime.date(2014, 11, 29), distributor_id='linuxmint', eol_date=datetime.date(2019, 4, 30), extended_eol_date=datetime.date(2019, 4, 30), is_lts=True, release_date=datetime.date(2014, 11, 29), series='rebecca', version=decimal.Decimal('17.1'), ), Release( codename='Rafaela', compatible_repository='trusty', created_date=datetime.date(2015, 6, 30), distributor_id='linuxmint', eol_date=datetime.date(2019, 4, 30), extended_eol_date=datetime.date(2019, 4, 30), is_lts=True, release_date=datetime.date(2015, 6, 30), series='rafaela', version=decimal.Decimal('17.2'), ), Release( codename='Rosa', compatible_repository='trusty', created_date=datetime.date(2015, 12, 4), distributor_id='linuxmint', eol_date=datetime.date(2019, 4, 30), extended_eol_date=datetime.date(2019, 4, 30), is_lts=True, release_date=datetime.date(2015, 12, 4), series='rosa', version=decimal.Decimal('17.3'), ), Release( codename='Sarah', compatible_repository='xenial', created_date=datetime.date(2016, 6, 30), distributor_id='linuxmint', eol_date=datetime.date(2021, 4, 30), extended_eol_date=datetime.date(2021, 4, 30), is_lts=True, release_date=datetime.date(2016, 6, 30), series='sarah', version=decimal.Decimal('18'), ), Release( codename='Serena', compatible_repository='xenial', created_date=datetime.date(2016, 12, 16), distributor_id='linuxmint', eol_date=datetime.date(2021, 4, 30), extended_eol_date=datetime.date(2021, 4, 30), is_lts=True, release_date=datetime.date(2016, 12, 16), series='serena', version=decimal.Decimal('18.1'), ), Release( codename='Sonya', compatible_repository='xenial', created_date=datetime.date(2017, 7, 2), distributor_id='linuxmint', eol_date=datetime.date(2021, 4, 30), extended_eol_date=datetime.date(2021, 4, 30), is_lts=True, release_date=datetime.date(2017, 7, 2), series='sonya', version=decimal.Decimal('18.2'), ), Release( codename='Sylvia', compatible_repository='xenial', created_date=datetime.date(2017, 11, 27), distributor_id='linuxmint', eol_date=datetime.date(2021, 4, 30), extended_eol_date=datetime.date(2021, 4, 30), is_lts=True, release_date=datetime.date(2017, 11, 27), series='sylvia', version=decimal.Decimal('18.3'), ), Release( codename='Tara', compatible_repository='bionic', created_date=datetime.date(2018, 6, 29), distributor_id='linuxmint', eol_date=datetime.date(2023, 4, 30), extended_eol_date=datetime.date(2023, 4, 30), is_lts=True, release_date=datetime.date(2018, 6, 29), series='tara', version=decimal.Decimal('19'), ), Release( codename='Tessa', compatible_repository='bionic', created_date=datetime.date(2018, 12, 19), distributor_id='linuxmint', eol_date=datetime.date(2023, 4, 30), extended_eol_date=datetime.date(2023, 4, 30), is_lts=True, release_date=datetime.date(2018, 12, 19), series='tessa', version=decimal.Decimal('19.1'), ), Release( codename='Tina', compatible_repository='bionic', created_date=datetime.date(2019, 8, 2), distributor_id='linuxmint', eol_date=datetime.date(2023, 4, 30), extended_eol_date=datetime.date(2023, 4, 30), is_lts=True, release_date=datetime.date(2019, 8, 2), series='tina', version=decimal.Decimal('19.2'), ), Release( codename='Warty Warthog', created_date=datetime.date(2004, 3, 5), distributor_id='ubuntu', eol_date=datetime.date(2006, 4, 30), is_lts=False, release_date=datetime.date(2004, 10, 20), series='warty', version=decimal.Decimal('4.10'), ), Release( codename='Hoary Hedgehog', created_date=datetime.date(2004, 10, 20), distributor_id='ubuntu', eol_date=datetime.date(2006, 10, 31), is_lts=False, release_date=datetime.date(2005, 4, 8), series='hoary', version=decimal.Decimal('5.04'), ), Release( codename='Breezy Badger', created_date=datetime.date(2005, 4, 8), distributor_id='ubuntu', eol_date=datetime.date(2007, 4, 13), is_lts=False, release_date=datetime.date(2005, 10, 12), series='breezy', version=decimal.Decimal('5.10'), ), Release( codename='Dapper Drake', created_date=datetime.date(2005, 10, 12), distributor_id='ubuntu', eol_date=datetime.date(2009, 7, 14), extended_eol_date=datetime.date(2011, 6, 1), is_lts=True, release_date=datetime.date(2006, 6, 1), series='dapper', version=decimal.Decimal('6.06'), ), Release( codename='Edgy Eft', created_date=datetime.date(2006, 6, 1), distributor_id='ubuntu', eol_date=datetime.date(2008, 4, 25), is_lts=False, release_date=datetime.date(2006, 10, 26), series='edgy', version=decimal.Decimal('6.10'), ), Release( codename='Feisty Fawn', created_date=datetime.date(2006, 10, 26), distributor_id='ubuntu', eol_date=datetime.date(2008, 10, 19), is_lts=False, release_date=datetime.date(2007, 4, 19), series='feisty', version=decimal.Decimal('7.04'), ), Release( codename='Gutsy Gibbon', created_date=datetime.date(2007, 4, 19), distributor_id='ubuntu', eol_date=datetime.date(2009, 4, 18), is_lts=False, release_date=datetime.date(2007, 10, 18), series='gutsy', version=decimal.Decimal('7.10'), ), Release( codename='Hardy Heron', created_date=datetime.date(2007, 10, 18), distributor_id='ubuntu', eol_date=datetime.date(2011, 5, 12), extended_eol_date=datetime.date(2013, 5, 9), is_lts=True, release_date=datetime.date(2008, 4, 24), series='hardy', version=decimal.Decimal('8.04'), ), Release( codename='Intrepid Ibex', created_date=datetime.date(2008, 4, 24), distributor_id='ubuntu', eol_date=datetime.date(2010, 4, 30), is_lts=False, release_date=datetime.date(2008, 10, 30), series='intrepid', version=decimal.Decimal('8.10'), ), Release( codename='Jaunty Jackalope', created_date=datetime.date(2008, 10, 30), distributor_id='ubuntu', eol_date=datetime.date(2010, 10, 23), is_lts=False, release_date=datetime.date(2009, 4, 23), series='jaunty', version=decimal.Decimal('9.04'), ), Release( codename='Karmic Koala', created_date=datetime.date(2009, 4, 23), distributor_id='ubuntu', eol_date=datetime.date(2011, 4, 29), is_lts=False, release_date=datetime.date(2009, 10, 29), series='karmic', version=decimal.Decimal('9.10'), ), Release( codename='Lucid Lynx', created_date=datetime.date(2009, 10, 29), distributor_id='ubuntu', eol_date=datetime.date(2013, 5, 9), extended_eol_date=datetime.date(2015, 4, 29), is_lts=True, release_date=datetime.date(2010, 4, 29), series='lucid', version=decimal.Decimal('10.04'), ), Release( codename='Maverick Meerkat', created_date=datetime.date(2010, 4, 29), distributor_id='ubuntu', eol_date=datetime.date(2012, 4, 10), is_lts=False, release_date=datetime.date(2010, 10, 10), series='maverick', version=decimal.Decimal('10.10'), ), Release( codename='Natty Narwhal', created_date=datetime.date(2010, 10, 10), distributor_id='ubuntu', eol_date=datetime.date(2012, 10, 28), is_lts=False, release_date=datetime.date(2011, 4, 28), series='natty', version=decimal.Decimal('11.04'), ), Release( codename='Oneiric Ocelot', created_date=datetime.date(2011, 4, 28), distributor_id='ubuntu', eol_date=datetime.date(2013, 5, 9), is_lts=False, release_date=datetime.date(2011, 10, 13), series='oneiric', version=decimal.Decimal('11.10'), ), Release( codename='Precise Pangolin', created_date=datetime.date(2011, 10, 13), distributor_id='ubuntu', eol_date=datetime.date(2017, 4, 26), extended_eol_date=datetime.date(2017, 4, 26), is_lts=True, release_date=datetime.date(2012, 4, 26), series='precise', version=decimal.Decimal('12.04'), ), Release( codename='Quantal Quetzal', created_date=datetime.date(2012, 4, 26), distributor_id='ubuntu', eol_date=datetime.date(2014, 5, 16), is_lts=False, release_date=datetime.date(2012, 10, 18), series='quantal', version=decimal.Decimal('12.10'), ), Release( codename='Raring Ringtail', created_date=datetime.date(2012, 10, 18), distributor_id='ubuntu', eol_date=datetime.date(2014, 1, 27), is_lts=False, release_date=datetime.date(2013, 4, 25), series='raring', version=decimal.Decimal('13.04'), ), Release( codename='Saucy Salamander', created_date=datetime.date(2013, 4, 25), distributor_id='ubuntu', eol_date=datetime.date(2014, 7, 17), is_lts=False, release_date=datetime.date(2013, 10, 17), series='saucy', version=decimal.Decimal('13.10'), ), Release( codename='Trusty Tahr', created_date=datetime.date(2013, 10, 17), distributor_id='ubuntu', eol_date=datetime.date(2019, 4, 25), extended_eol_date=datetime.date(2019, 4, 25), is_lts=True, release_date=datetime.date(2014, 4, 17), series='trusty', version=decimal.Decimal('14.04'), ), Release( codename='Utopic Unicorn', created_date=datetime.date(2014, 4, 17), distributor_id='ubuntu', eol_date=datetime.date(2015, 7, 23), is_lts=False, release_date=datetime.date(2014, 10, 23), series='utopic', version=decimal.Decimal('14.10'), ), Release( codename='Vivid Vervet', created_date=datetime.date(2014, 10, 23), distributor_id='ubuntu', eol_date=datetime.date(2016, 1, 23), is_lts=False, release_date=datetime.date(2015, 4, 23), series='vivid', version=decimal.Decimal('15.04'), ), Release( codename='Wily Werewolf', created_date=datetime.date(2015, 4, 23), distributor_id='ubuntu', eol_date=datetime.date(2016, 7, 22), is_lts=False, release_date=datetime.date(2015, 10, 22), series='wily', version=decimal.Decimal('15.10'), ), Release( codename='Xenial Xerus', created_date=datetime.date(2015, 10, 22), distributor_id='ubuntu', eol_date=datetime.date(2021, 4, 21), extended_eol_date=datetime.date(2021, 4, 21), is_lts=True, release_date=datetime.date(2016, 4, 21), series='xenial', version=decimal.Decimal('16.04'), ), Release( codename='Yakkety Yak', created_date=datetime.date(2016, 4, 21), distributor_id='ubuntu', eol_date=datetime.date(2017, 7, 20), is_lts=False, release_date=datetime.date(2016, 10, 13), series='yakkety', version=decimal.Decimal('16.10'), ), Release( codename='Zesty Zapus', created_date=datetime.date(2016, 10, 13), distributor_id='ubuntu', eol_date=datetime.date(2018, 1, 13), is_lts=False, release_date=datetime.date(2017, 4, 13), series='zesty', version=decimal.Decimal('17.04'), ), Release( codename='Artful Aardvark', created_date=datetime.date(2017, 4, 13), distributor_id='ubuntu', eol_date=datetime.date(2018, 7, 19), is_lts=False, release_date=datetime.date(2017, 10, 19), series='artful', version=decimal.Decimal('17.10'), ), Release( codename='Bionic Beaver', created_date=datetime.date(2017, 10, 19), distributor_id='ubuntu', eol_date=datetime.date(2023, 4, 26), extended_eol_date=datetime.date(2023, 4, 26), is_lts=True, release_date=datetime.date(2018, 4, 26), series='bionic', version=decimal.Decimal('18.04'), ), Release( codename='Cosmic Cuttlefish', created_date=datetime.date(2018, 4, 26), distributor_id='ubuntu', eol_date=datetime.date(2019, 7, 18), is_lts=False, release_date=datetime.date(2018, 10, 18), series='cosmic', version=decimal.Decimal('18.10'), ), Release( codename='Disco Dingo', created_date=datetime.date(2018, 10, 18), distributor_id='ubuntu', eol_date=datetime.date(2020, 1, 18), is_lts=False, release_date=datetime.date(2019, 4, 18), series='disco', version=decimal.Decimal('19.04'), ), Release( codename='Eoan Ermine', created_date=datetime.date(2019, 4, 18), distributor_id='ubuntu', eol_date=datetime.date(2020, 7, 17), is_lts=False, release_date=datetime.date(2019, 10, 17), series='eoan', version=decimal.Decimal('19.10'), ), Release( codename='Focal Fossa', created_date=datetime.date(2019, 10, 17), distributor_id='ubuntu', eol_date=datetime.date(2025, 4, 23), extended_eol_date=datetime.date(2025, 4, 23), is_lts=True, release_date=datetime.date(2020, 4, 23), series='focal', version=decimal.Decimal('20.04'), ), Release( codename='Groovy Gorilla', created_date=datetime.date(2020, 4, 23), distributor_id='ubuntu', eol_date=datetime.date(2021, 7, 22), is_lts=False, release_date=datetime.date(2020, 10, 22), series='groovy', version=decimal.Decimal('20.10'), ), Release( codename='Ada', compatible_repository='Kubuntu 6.06', created_date=datetime.date(2006, 8, 27), distributor_id='linuxmint', eol_date=datetime.date(2008, 4, 30), extended_eol_date=datetime.date(2008, 4, 30), is_lts=False, release_date=datetime.date(2006, 8, 27), series='ada', version=decimal.Decimal('1.0'), ), Release( codename='Barbara', compatible_repository='edgy', created_date=datetime.date(2006, 11, 13), distributor_id='linuxmint', eol_date=datetime.date(2008, 4, 30), extended_eol_date=datetime.date(2008, 4, 30), is_lts=False, release_date=datetime.date(2006, 11, 13), series='barbara', version=decimal.Decimal('2.0'), ), Release( codename='Bea', compatible_repository='edgy', created_date=datetime.date(2006, 12, 20), distributor_id='linuxmint', eol_date=datetime.date(2008, 4, 30), extended_eol_date=datetime.date(2008, 4, 30), is_lts=False, release_date=datetime.date(2006, 12, 20), series='bea', version=decimal.Decimal('2.1'), ), Release( codename='Bianca', compatible_repository='edgy', created_date=datetime.date(2007, 2, 20), distributor_id='linuxmint', eol_date=datetime.date(2008, 4, 30), extended_eol_date=datetime.date(2008, 4, 30), is_lts=False, release_date=datetime.date(2007, 2, 20), series='bianca', version=decimal.Decimal('2.2'), ), Release( codename='Cassandra', compatible_repository='feisty', created_date=datetime.date(2007, 5, 30), distributor_id='linuxmint', eol_date=datetime.date(2008, 10, 30), extended_eol_date=datetime.date(2008, 10, 30), is_lts=False, release_date=datetime.date(2007, 5, 30), series='cassandra', version=decimal.Decimal('3.0'), ), Release( codename='Celena', compatible_repository='feisty', created_date=datetime.date(2007, 9, 24), distributor_id='linuxmint', eol_date=datetime.date(2008, 10, 30), extended_eol_date=datetime.date(2008, 10, 30), is_lts=False, release_date=datetime.date(2007, 9, 24), series='celena', version=decimal.Decimal('3.1'), ), Release( codename='Daryna', compatible_repository='gutsy', created_date=datetime.date(2007, 10, 15), distributor_id='linuxmint', eol_date=datetime.date(2009, 4, 30), extended_eol_date=datetime.date(2009, 4, 30), is_lts=False, release_date=datetime.date(2007, 10, 15), series='daryna', version=decimal.Decimal('4.0'), ), Release( codename='Elyssa', compatible_repository='hardy', created_date=datetime.date(2008, 6, 8), distributor_id='linuxmint', eol_date=datetime.date(2011, 4, 30), extended_eol_date=datetime.date(2011, 4, 30), is_lts=True, release_date=datetime.date(2008, 6, 8), series='elyssa', version=decimal.Decimal('5'), ), Release( codename='Felicia', compatible_repository='intrepid', created_date=datetime.date(2008, 12, 15), distributor_id='linuxmint', eol_date=datetime.date(2010, 4, 30), extended_eol_date=datetime.date(2010, 4, 30), is_lts=False, release_date=datetime.date(2008, 12, 15), series='felicia', version=decimal.Decimal('6'), ), Release( codename='Gloria', compatible_repository='jaunty', created_date=datetime.date(2009, 5, 26), distributor_id='linuxmint', eol_date=datetime.date(2010, 10, 30), extended_eol_date=datetime.date(2010, 10, 30), is_lts=False, release_date=datetime.date(2009, 5, 26), series='gloria', version=decimal.Decimal('7'), ), Release( codename='Helena', compatible_repository='karmic', created_date=datetime.date(2009, 11, 28), distributor_id='linuxmint', eol_date=datetime.date(2011, 4, 30), extended_eol_date=datetime.date(2011, 4, 30), is_lts=False, release_date=datetime.date(2009, 11, 28), series='helena', version=decimal.Decimal('8'), ), Release( codename='Isadora', compatible_repository='lucid', created_date=datetime.date(2010, 5, 18), distributor_id='linuxmint', eol_date=datetime.date(2013, 4, 30), extended_eol_date=datetime.date(2013, 4, 30), is_lts=True, release_date=datetime.date(2010, 5, 18), series='isadora', version=decimal.Decimal('9'), ), Release( codename='Julia', compatible_repository='maverick', created_date=datetime.date(2010, 11, 12), distributor_id='linuxmint', eol_date=datetime.date(2012, 4, 30), extended_eol_date=datetime.date(2012, 4, 30), is_lts=False, release_date=datetime.date(2010, 11, 12), series='julia', version=decimal.Decimal('10'), ), Release( codename='Katya', compatible_repository='natty', created_date=datetime.date(2011, 5, 26), distributor_id='linuxmint', eol_date=datetime.date(2012, 10, 30), extended_eol_date=datetime.date(2012, 10, 30), is_lts=False, release_date=datetime.date(2011, 5, 26), series='katya', version=decimal.Decimal('11'), ), Release( codename='Lisa', compatible_repository='oneiric', created_date=datetime.date(2011, 11, 26), distributor_id='linuxmint', eol_date=datetime.date(2013, 4, 30), extended_eol_date=datetime.date(2013, 4, 30), is_lts=False, release_date=datetime.date(2011, 11, 26), series='lisa', version=decimal.Decimal('12'), ), Release( codename='Maya', compatible_repository='precise', created_date=datetime.date(2012, 5, 23), distributor_id='linuxmint', eol_date=datetime.date(2017, 4, 30), extended_eol_date=datetime.date(2017, 4, 30), is_lts=True, release_date=datetime.date(2012, 5, 23), series='maya', version=decimal.Decimal('13'), ), Release( codename='Nadia', compatible_repository='quantal', created_date=datetime.date(2012, 11, 20), distributor_id='linuxmint', eol_date=datetime.date(2014, 5, 30), extended_eol_date=datetime.date(2014, 5, 30), is_lts=False, release_date=datetime.date(2012, 11, 20), series='nadia', version=decimal.Decimal('14'), ), Release( codename='Olivia', compatible_repository='raring', created_date=datetime.date(2013, 5, 29), distributor_id='linuxmint', eol_date=datetime.date(2014, 1, 30), extended_eol_date=datetime.date(2014, 1, 30), is_lts=False, release_date=datetime.date(2013, 5, 29), series='olivia', version=decimal.Decimal('15'), ), Release( codename='Petra', compatible_repository='saucy', created_date=datetime.date(2013, 11, 30), distributor_id='linuxmint', eol_date=datetime.date(2014, 7, 30), extended_eol_date=datetime.date(2014, 7, 30), is_lts=False, release_date=datetime.date(2013, 11, 30), series='petra', version=decimal.Decimal('16'), ), Release( codename='Qiana', compatible_repository='trusty', created_date=datetime.date(2014, 5, 31), distributor_id='linuxmint', eol_date=datetime.date(2019, 4, 30), extended_eol_date=datetime.date(2019, 4, 30), is_lts=True, release_date=datetime.date(2014, 5, 31), series='qiana', version=decimal.Decimal('17'), ), Release( codename='Rebecca', compatible_repository='trusty', created_date=datetime.date(2014, 11, 29), distributor_id='linuxmint', eol_date=datetime.date(2019, 4, 30), extended_eol_date=datetime.date(2019, 4, 30), is_lts=True, release_date=datetime.date(2014, 11, 29), series='rebecca', version=decimal.Decimal('17.1'), ), Release( codename='Rafaela', compatible_repository='trusty', created_date=datetime.date(2015, 6, 30), distributor_id='linuxmint', eol_date=datetime.date(2019, 4, 30), extended_eol_date=datetime.date(2019, 4, 30), is_lts=True, release_date=datetime.date(2015, 6, 30), series='rafaela', version=decimal.Decimal('17.2'), ), Release( codename='Rosa', compatible_repository='trusty', created_date=datetime.date(2015, 12, 4), distributor_id='linuxmint', eol_date=datetime.date(2019, 4, 30), extended_eol_date=datetime.date(2019, 4, 30), is_lts=True, release_date=datetime.date(2015, 12, 4), series='rosa', version=decimal.Decimal('17.3'), ), Release( codename='Sarah', compatible_repository='xenial', created_date=datetime.date(2016, 6, 30), distributor_id='linuxmint', eol_date=datetime.date(2021, 4, 30), extended_eol_date=datetime.date(2021, 4, 30), is_lts=True, release_date=datetime.date(2016, 6, 30), series='sarah', version=decimal.Decimal('18'), ), Release( codename='Serena', compatible_repository='xenial', created_date=datetime.date(2016, 12, 16), distributor_id='linuxmint', eol_date=datetime.date(2021, 4, 30), extended_eol_date=datetime.date(2021, 4, 30), is_lts=True, release_date=datetime.date(2016, 12, 16), series='serena', version=decimal.Decimal('18.1'), ), Release( codename='Sonya', compatible_repository='xenial', created_date=datetime.date(2017, 7, 2), distributor_id='linuxmint', eol_date=datetime.date(2021, 4, 30), extended_eol_date=datetime.date(2021, 4, 30), is_lts=True, release_date=datetime.date(2017, 7, 2), series='sonya', version=decimal.Decimal('18.2'), ), Release( codename='Sylvia', compatible_repository='xenial', created_date=datetime.date(2017, 11, 27), distributor_id='linuxmint', eol_date=datetime.date(2021, 4, 30), extended_eol_date=datetime.date(2021, 4, 30), is_lts=True, release_date=datetime.date(2017, 11, 27), series='sylvia', version=decimal.Decimal('18.3'), ), Release( codename='Tara', compatible_repository='bionic', created_date=datetime.date(2018, 6, 29), distributor_id='linuxmint', eol_date=datetime.date(2023, 4, 30), extended_eol_date=datetime.date(2023, 4, 30), is_lts=True, release_date=datetime.date(2018, 6, 29), series='tara', version=decimal.Decimal('19'), ), Release( codename='Tessa', compatible_repository='bionic', created_date=datetime.date(2018, 12, 19), distributor_id='linuxmint', eol_date=datetime.date(2023, 4, 30), extended_eol_date=datetime.date(2023, 4, 30), is_lts=True, release_date=datetime.date(2018, 12, 19), series='tessa', version=decimal.Decimal('19.1'), ), Release( codename='Tina', compatible_repository='bionic', created_date=datetime.date(2019, 8, 2), distributor_id='linuxmint', eol_date=datetime.date(2023, 4, 30), extended_eol_date=datetime.date(2023, 4, 30), is_lts=True, release_date=datetime.date(2019, 8, 2), series='tina', version=decimal.Decimal('19.2'), ), Release( codename='Tricia', compatible_repository='bionic', created_date=datetime.date(2019, 12, 18), distributor_id='linuxmint', eol_date=datetime.date(2023, 4, 30), extended_eol_date=datetime.date(2023, 4, 30), is_lts=True, release_date=datetime.date(2019, 12, 18), series='tricia', version=decimal.Decimal('19.3'), ), Release( codename='Ulyana', compatible_repository='focal', created_date=datetime.date(2020, 6, 30), distributor_id='linuxmint', eol_date=datetime.date(2025, 4, 30), extended_eol_date=datetime.date(2025, 4, 30), is_lts=True, release_date=datetime.date(2020, 6, 30), series='ulyana', version=decimal.Decimal('20'), ), ] # [[[end]]]