comparison planemo/lib/python3.7/site-packages/pip/_vendor/distro.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
1 # Copyright 2015,2016,2017 Nir Cohen
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """
16 The ``distro`` package (``distro`` stands for Linux Distribution) provides
17 information about the Linux distribution it runs on, such as a reliable
18 machine-readable distro ID, or version information.
19
20 It is the recommended replacement for Python's original
21 :py:func:`platform.linux_distribution` function, but it provides much more
22 functionality. An alternative implementation became necessary because Python
23 3.5 deprecated this function, and Python 3.8 will remove it altogether.
24 Its predecessor function :py:func:`platform.dist` was already
25 deprecated since Python 2.6 and will also be removed in Python 3.8.
26 Still, there are many cases in which access to OS distribution information
27 is needed. See `Python issue 1322 <https://bugs.python.org/issue1322>`_ for
28 more information.
29 """
30
31 import os
32 import re
33 import sys
34 import json
35 import shlex
36 import logging
37 import argparse
38 import subprocess
39
40
41 _UNIXCONFDIR = os.environ.get('UNIXCONFDIR', '/etc')
42 _OS_RELEASE_BASENAME = 'os-release'
43
44 #: Translation table for normalizing the "ID" attribute defined in os-release
45 #: files, for use by the :func:`distro.id` method.
46 #:
47 #: * Key: Value as defined in the os-release file, translated to lower case,
48 #: with blanks translated to underscores.
49 #:
50 #: * Value: Normalized value.
51 NORMALIZED_OS_ID = {
52 'ol': 'oracle', # Oracle Enterprise Linux
53 }
54
55 #: Translation table for normalizing the "Distributor ID" attribute returned by
56 #: the lsb_release command, for use by the :func:`distro.id` method.
57 #:
58 #: * Key: Value as returned by the lsb_release command, translated to lower
59 #: case, with blanks translated to underscores.
60 #:
61 #: * Value: Normalized value.
62 NORMALIZED_LSB_ID = {
63 'enterpriseenterprise': 'oracle', # Oracle Enterprise Linux
64 'redhatenterpriseworkstation': 'rhel', # RHEL 6, 7 Workstation
65 'redhatenterpriseserver': 'rhel', # RHEL 6, 7 Server
66 }
67
68 #: Translation table for normalizing the distro ID derived from the file name
69 #: of distro release files, for use by the :func:`distro.id` method.
70 #:
71 #: * Key: Value as derived from the file name of a distro release file,
72 #: translated to lower case, with blanks translated to underscores.
73 #:
74 #: * Value: Normalized value.
75 NORMALIZED_DISTRO_ID = {
76 'redhat': 'rhel', # RHEL 6.x, 7.x
77 }
78
79 # Pattern for content of distro release file (reversed)
80 _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile(
81 r'(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)')
82
83 # Pattern for base file name of distro release file
84 _DISTRO_RELEASE_BASENAME_PATTERN = re.compile(
85 r'(\w+)[-_](release|version)$')
86
87 # Base file names to be ignored when searching for distro release file
88 _DISTRO_RELEASE_IGNORE_BASENAMES = (
89 'debian_version',
90 'lsb-release',
91 'oem-release',
92 _OS_RELEASE_BASENAME,
93 'system-release'
94 )
95
96
97 def linux_distribution(full_distribution_name=True):
98 """
99 Return information about the current OS distribution as a tuple
100 ``(id_name, version, codename)`` with items as follows:
101
102 * ``id_name``: If *full_distribution_name* is false, the result of
103 :func:`distro.id`. Otherwise, the result of :func:`distro.name`.
104
105 * ``version``: The result of :func:`distro.version`.
106
107 * ``codename``: The result of :func:`distro.codename`.
108
109 The interface of this function is compatible with the original
110 :py:func:`platform.linux_distribution` function, supporting a subset of
111 its parameters.
112
113 The data it returns may not exactly be the same, because it uses more data
114 sources than the original function, and that may lead to different data if
115 the OS distribution is not consistent across multiple data sources it
116 provides (there are indeed such distributions ...).
117
118 Another reason for differences is the fact that the :func:`distro.id`
119 method normalizes the distro ID string to a reliable machine-readable value
120 for a number of popular OS distributions.
121 """
122 return _distro.linux_distribution(full_distribution_name)
123
124
125 def id():
126 """
127 Return the distro ID of the current distribution, as a
128 machine-readable string.
129
130 For a number of OS distributions, the returned distro ID value is
131 *reliable*, in the sense that it is documented and that it does not change
132 across releases of the distribution.
133
134 This package maintains the following reliable distro ID values:
135
136 ============== =========================================
137 Distro ID Distribution
138 ============== =========================================
139 "ubuntu" Ubuntu
140 "debian" Debian
141 "rhel" RedHat Enterprise Linux
142 "centos" CentOS
143 "fedora" Fedora
144 "sles" SUSE Linux Enterprise Server
145 "opensuse" openSUSE
146 "amazon" Amazon Linux
147 "arch" Arch Linux
148 "cloudlinux" CloudLinux OS
149 "exherbo" Exherbo Linux
150 "gentoo" GenToo Linux
151 "ibm_powerkvm" IBM PowerKVM
152 "kvmibm" KVM for IBM z Systems
153 "linuxmint" Linux Mint
154 "mageia" Mageia
155 "mandriva" Mandriva Linux
156 "parallels" Parallels
157 "pidora" Pidora
158 "raspbian" Raspbian
159 "oracle" Oracle Linux (and Oracle Enterprise Linux)
160 "scientific" Scientific Linux
161 "slackware" Slackware
162 "xenserver" XenServer
163 "openbsd" OpenBSD
164 "netbsd" NetBSD
165 "freebsd" FreeBSD
166 ============== =========================================
167
168 If you have a need to get distros for reliable IDs added into this set,
169 or if you find that the :func:`distro.id` function returns a different
170 distro ID for one of the listed distros, please create an issue in the
171 `distro issue tracker`_.
172
173 **Lookup hierarchy and transformations:**
174
175 First, the ID is obtained from the following sources, in the specified
176 order. The first available and non-empty value is used:
177
178 * the value of the "ID" attribute of the os-release file,
179
180 * the value of the "Distributor ID" attribute returned by the lsb_release
181 command,
182
183 * the first part of the file name of the distro release file,
184
185 The so determined ID value then passes the following transformations,
186 before it is returned by this method:
187
188 * it is translated to lower case,
189
190 * blanks (which should not be there anyway) are translated to underscores,
191
192 * a normalization of the ID is performed, based upon
193 `normalization tables`_. The purpose of this normalization is to ensure
194 that the ID is as reliable as possible, even across incompatible changes
195 in the OS distributions. A common reason for an incompatible change is
196 the addition of an os-release file, or the addition of the lsb_release
197 command, with ID values that differ from what was previously determined
198 from the distro release file name.
199 """
200 return _distro.id()
201
202
203 def name(pretty=False):
204 """
205 Return the name of the current OS distribution, as a human-readable
206 string.
207
208 If *pretty* is false, the name is returned without version or codename.
209 (e.g. "CentOS Linux")
210
211 If *pretty* is true, the version and codename are appended.
212 (e.g. "CentOS Linux 7.1.1503 (Core)")
213
214 **Lookup hierarchy:**
215
216 The name is obtained from the following sources, in the specified order.
217 The first available and non-empty value is used:
218
219 * If *pretty* is false:
220
221 - the value of the "NAME" attribute of the os-release file,
222
223 - the value of the "Distributor ID" attribute returned by the lsb_release
224 command,
225
226 - the value of the "<name>" field of the distro release file.
227
228 * If *pretty* is true:
229
230 - the value of the "PRETTY_NAME" attribute of the os-release file,
231
232 - the value of the "Description" attribute returned by the lsb_release
233 command,
234
235 - the value of the "<name>" field of the distro release file, appended
236 with the value of the pretty version ("<version_id>" and "<codename>"
237 fields) of the distro release file, if available.
238 """
239 return _distro.name(pretty)
240
241
242 def version(pretty=False, best=False):
243 """
244 Return the version of the current OS distribution, as a human-readable
245 string.
246
247 If *pretty* is false, the version is returned without codename (e.g.
248 "7.0").
249
250 If *pretty* is true, the codename in parenthesis is appended, if the
251 codename is non-empty (e.g. "7.0 (Maipo)").
252
253 Some distributions provide version numbers with different precisions in
254 the different sources of distribution information. Examining the different
255 sources in a fixed priority order does not always yield the most precise
256 version (e.g. for Debian 8.2, or CentOS 7.1).
257
258 The *best* parameter can be used to control the approach for the returned
259 version:
260
261 If *best* is false, the first non-empty version number in priority order of
262 the examined sources is returned.
263
264 If *best* is true, the most precise version number out of all examined
265 sources is returned.
266
267 **Lookup hierarchy:**
268
269 In all cases, the version number is obtained from the following sources.
270 If *best* is false, this order represents the priority order:
271
272 * the value of the "VERSION_ID" attribute of the os-release file,
273 * the value of the "Release" attribute returned by the lsb_release
274 command,
275 * the version number parsed from the "<version_id>" field of the first line
276 of the distro release file,
277 * the version number parsed from the "PRETTY_NAME" attribute of the
278 os-release file, if it follows the format of the distro release files.
279 * the version number parsed from the "Description" attribute returned by
280 the lsb_release command, if it follows the format of the distro release
281 files.
282 """
283 return _distro.version(pretty, best)
284
285
286 def version_parts(best=False):
287 """
288 Return the version of the current OS distribution as a tuple
289 ``(major, minor, build_number)`` with items as follows:
290
291 * ``major``: The result of :func:`distro.major_version`.
292
293 * ``minor``: The result of :func:`distro.minor_version`.
294
295 * ``build_number``: The result of :func:`distro.build_number`.
296
297 For a description of the *best* parameter, see the :func:`distro.version`
298 method.
299 """
300 return _distro.version_parts(best)
301
302
303 def major_version(best=False):
304 """
305 Return the major version of the current OS distribution, as a string,
306 if provided.
307 Otherwise, the empty string is returned. The major version is the first
308 part of the dot-separated version string.
309
310 For a description of the *best* parameter, see the :func:`distro.version`
311 method.
312 """
313 return _distro.major_version(best)
314
315
316 def minor_version(best=False):
317 """
318 Return the minor version of the current OS distribution, as a string,
319 if provided.
320 Otherwise, the empty string is returned. The minor version is the second
321 part of the dot-separated version string.
322
323 For a description of the *best* parameter, see the :func:`distro.version`
324 method.
325 """
326 return _distro.minor_version(best)
327
328
329 def build_number(best=False):
330 """
331 Return the build number of the current OS distribution, as a string,
332 if provided.
333 Otherwise, the empty string is returned. The build number is the third part
334 of the dot-separated version string.
335
336 For a description of the *best* parameter, see the :func:`distro.version`
337 method.
338 """
339 return _distro.build_number(best)
340
341
342 def like():
343 """
344 Return a space-separated list of distro IDs of distributions that are
345 closely related to the current OS distribution in regards to packaging
346 and programming interfaces, for example distributions the current
347 distribution is a derivative from.
348
349 **Lookup hierarchy:**
350
351 This information item is only provided by the os-release file.
352 For details, see the description of the "ID_LIKE" attribute in the
353 `os-release man page
354 <http://www.freedesktop.org/software/systemd/man/os-release.html>`_.
355 """
356 return _distro.like()
357
358
359 def codename():
360 """
361 Return the codename for the release of the current OS distribution,
362 as a string.
363
364 If the distribution does not have a codename, an empty string is returned.
365
366 Note that the returned codename is not always really a codename. For
367 example, openSUSE returns "x86_64". This function does not handle such
368 cases in any special way and just returns the string it finds, if any.
369
370 **Lookup hierarchy:**
371
372 * the codename within the "VERSION" attribute of the os-release file, if
373 provided,
374
375 * the value of the "Codename" attribute returned by the lsb_release
376 command,
377
378 * the value of the "<codename>" field of the distro release file.
379 """
380 return _distro.codename()
381
382
383 def info(pretty=False, best=False):
384 """
385 Return certain machine-readable information items about the current OS
386 distribution in a dictionary, as shown in the following example:
387
388 .. sourcecode:: python
389
390 {
391 'id': 'rhel',
392 'version': '7.0',
393 'version_parts': {
394 'major': '7',
395 'minor': '0',
396 'build_number': ''
397 },
398 'like': 'fedora',
399 'codename': 'Maipo'
400 }
401
402 The dictionary structure and keys are always the same, regardless of which
403 information items are available in the underlying data sources. The values
404 for the various keys are as follows:
405
406 * ``id``: The result of :func:`distro.id`.
407
408 * ``version``: The result of :func:`distro.version`.
409
410 * ``version_parts -> major``: The result of :func:`distro.major_version`.
411
412 * ``version_parts -> minor``: The result of :func:`distro.minor_version`.
413
414 * ``version_parts -> build_number``: The result of
415 :func:`distro.build_number`.
416
417 * ``like``: The result of :func:`distro.like`.
418
419 * ``codename``: The result of :func:`distro.codename`.
420
421 For a description of the *pretty* and *best* parameters, see the
422 :func:`distro.version` method.
423 """
424 return _distro.info(pretty, best)
425
426
427 def os_release_info():
428 """
429 Return a dictionary containing key-value pairs for the information items
430 from the os-release file data source of the current OS distribution.
431
432 See `os-release file`_ for details about these information items.
433 """
434 return _distro.os_release_info()
435
436
437 def lsb_release_info():
438 """
439 Return a dictionary containing key-value pairs for the information items
440 from the lsb_release command data source of the current OS distribution.
441
442 See `lsb_release command output`_ for details about these information
443 items.
444 """
445 return _distro.lsb_release_info()
446
447
448 def distro_release_info():
449 """
450 Return a dictionary containing key-value pairs for the information items
451 from the distro release file data source of the current OS distribution.
452
453 See `distro release file`_ for details about these information items.
454 """
455 return _distro.distro_release_info()
456
457
458 def uname_info():
459 """
460 Return a dictionary containing key-value pairs for the information items
461 from the distro release file data source of the current OS distribution.
462 """
463 return _distro.uname_info()
464
465
466 def os_release_attr(attribute):
467 """
468 Return a single named information item from the os-release file data source
469 of the current OS distribution.
470
471 Parameters:
472
473 * ``attribute`` (string): Key of the information item.
474
475 Returns:
476
477 * (string): Value of the information item, if the item exists.
478 The empty string, if the item does not exist.
479
480 See `os-release file`_ for details about these information items.
481 """
482 return _distro.os_release_attr(attribute)
483
484
485 def lsb_release_attr(attribute):
486 """
487 Return a single named information item from the lsb_release command output
488 data source of the current OS distribution.
489
490 Parameters:
491
492 * ``attribute`` (string): Key of the information item.
493
494 Returns:
495
496 * (string): Value of the information item, if the item exists.
497 The empty string, if the item does not exist.
498
499 See `lsb_release command output`_ for details about these information
500 items.
501 """
502 return _distro.lsb_release_attr(attribute)
503
504
505 def distro_release_attr(attribute):
506 """
507 Return a single named information item from the distro release file
508 data source of the current OS distribution.
509
510 Parameters:
511
512 * ``attribute`` (string): Key of the information item.
513
514 Returns:
515
516 * (string): Value of the information item, if the item exists.
517 The empty string, if the item does not exist.
518
519 See `distro release file`_ for details about these information items.
520 """
521 return _distro.distro_release_attr(attribute)
522
523
524 def uname_attr(attribute):
525 """
526 Return a single named information item from the distro release file
527 data source of the current OS distribution.
528
529 Parameters:
530
531 * ``attribute`` (string): Key of the information item.
532
533 Returns:
534
535 * (string): Value of the information item, if the item exists.
536 The empty string, if the item does not exist.
537 """
538 return _distro.uname_attr(attribute)
539
540
541 class cached_property(object):
542 """A version of @property which caches the value. On access, it calls the
543 underlying function and sets the value in `__dict__` so future accesses
544 will not re-call the property.
545 """
546 def __init__(self, f):
547 self._fname = f.__name__
548 self._f = f
549
550 def __get__(self, obj, owner):
551 assert obj is not None, 'call {} on an instance'.format(self._fname)
552 ret = obj.__dict__[self._fname] = self._f(obj)
553 return ret
554
555
556 class LinuxDistribution(object):
557 """
558 Provides information about a OS distribution.
559
560 This package creates a private module-global instance of this class with
561 default initialization arguments, that is used by the
562 `consolidated accessor functions`_ and `single source accessor functions`_.
563 By using default initialization arguments, that module-global instance
564 returns data about the current OS distribution (i.e. the distro this
565 package runs on).
566
567 Normally, it is not necessary to create additional instances of this class.
568 However, in situations where control is needed over the exact data sources
569 that are used, instances of this class can be created with a specific
570 distro release file, or a specific os-release file, or without invoking the
571 lsb_release command.
572 """
573
574 def __init__(self,
575 include_lsb=True,
576 os_release_file='',
577 distro_release_file='',
578 include_uname=True):
579 """
580 The initialization method of this class gathers information from the
581 available data sources, and stores that in private instance attributes.
582 Subsequent access to the information items uses these private instance
583 attributes, so that the data sources are read only once.
584
585 Parameters:
586
587 * ``include_lsb`` (bool): Controls whether the
588 `lsb_release command output`_ is included as a data source.
589
590 If the lsb_release command is not available in the program execution
591 path, the data source for the lsb_release command will be empty.
592
593 * ``os_release_file`` (string): The path name of the
594 `os-release file`_ that is to be used as a data source.
595
596 An empty string (the default) will cause the default path name to
597 be used (see `os-release file`_ for details).
598
599 If the specified or defaulted os-release file does not exist, the
600 data source for the os-release file will be empty.
601
602 * ``distro_release_file`` (string): The path name of the
603 `distro release file`_ that is to be used as a data source.
604
605 An empty string (the default) will cause a default search algorithm
606 to be used (see `distro release file`_ for details).
607
608 If the specified distro release file does not exist, or if no default
609 distro release file can be found, the data source for the distro
610 release file will be empty.
611
612 * ``include_name`` (bool): Controls whether uname command output is
613 included as a data source. If the uname command is not available in
614 the program execution path the data source for the uname command will
615 be empty.
616
617 Public instance attributes:
618
619 * ``os_release_file`` (string): The path name of the
620 `os-release file`_ that is actually used as a data source. The
621 empty string if no distro release file is used as a data source.
622
623 * ``distro_release_file`` (string): The path name of the
624 `distro release file`_ that is actually used as a data source. The
625 empty string if no distro release file is used as a data source.
626
627 * ``include_lsb`` (bool): The result of the ``include_lsb`` parameter.
628 This controls whether the lsb information will be loaded.
629
630 * ``include_uname`` (bool): The result of the ``include_uname``
631 parameter. This controls whether the uname information will
632 be loaded.
633
634 Raises:
635
636 * :py:exc:`IOError`: Some I/O issue with an os-release file or distro
637 release file.
638
639 * :py:exc:`subprocess.CalledProcessError`: The lsb_release command had
640 some issue (other than not being available in the program execution
641 path).
642
643 * :py:exc:`UnicodeError`: A data source has unexpected characters or
644 uses an unexpected encoding.
645 """
646 self.os_release_file = os_release_file or \
647 os.path.join(_UNIXCONFDIR, _OS_RELEASE_BASENAME)
648 self.distro_release_file = distro_release_file or '' # updated later
649 self.include_lsb = include_lsb
650 self.include_uname = include_uname
651
652 def __repr__(self):
653 """Return repr of all info
654 """
655 return \
656 "LinuxDistribution(" \
657 "os_release_file={self.os_release_file!r}, " \
658 "distro_release_file={self.distro_release_file!r}, " \
659 "include_lsb={self.include_lsb!r}, " \
660 "include_uname={self.include_uname!r}, " \
661 "_os_release_info={self._os_release_info!r}, " \
662 "_lsb_release_info={self._lsb_release_info!r}, " \
663 "_distro_release_info={self._distro_release_info!r}, " \
664 "_uname_info={self._uname_info!r})".format(
665 self=self)
666
667 def linux_distribution(self, full_distribution_name=True):
668 """
669 Return information about the OS distribution that is compatible
670 with Python's :func:`platform.linux_distribution`, supporting a subset
671 of its parameters.
672
673 For details, see :func:`distro.linux_distribution`.
674 """
675 return (
676 self.name() if full_distribution_name else self.id(),
677 self.version(),
678 self.codename()
679 )
680
681 def id(self):
682 """Return the distro ID of the OS distribution, as a string.
683
684 For details, see :func:`distro.id`.
685 """
686 def normalize(distro_id, table):
687 distro_id = distro_id.lower().replace(' ', '_')
688 return table.get(distro_id, distro_id)
689
690 distro_id = self.os_release_attr('id')
691 if distro_id:
692 return normalize(distro_id, NORMALIZED_OS_ID)
693
694 distro_id = self.lsb_release_attr('distributor_id')
695 if distro_id:
696 return normalize(distro_id, NORMALIZED_LSB_ID)
697
698 distro_id = self.distro_release_attr('id')
699 if distro_id:
700 return normalize(distro_id, NORMALIZED_DISTRO_ID)
701
702 distro_id = self.uname_attr('id')
703 if distro_id:
704 return normalize(distro_id, NORMALIZED_DISTRO_ID)
705
706 return ''
707
708 def name(self, pretty=False):
709 """
710 Return the name of the OS distribution, as a string.
711
712 For details, see :func:`distro.name`.
713 """
714 name = self.os_release_attr('name') \
715 or self.lsb_release_attr('distributor_id') \
716 or self.distro_release_attr('name') \
717 or self.uname_attr('name')
718 if pretty:
719 name = self.os_release_attr('pretty_name') \
720 or self.lsb_release_attr('description')
721 if not name:
722 name = self.distro_release_attr('name') \
723 or self.uname_attr('name')
724 version = self.version(pretty=True)
725 if version:
726 name = name + ' ' + version
727 return name or ''
728
729 def version(self, pretty=False, best=False):
730 """
731 Return the version of the OS distribution, as a string.
732
733 For details, see :func:`distro.version`.
734 """
735 versions = [
736 self.os_release_attr('version_id'),
737 self.lsb_release_attr('release'),
738 self.distro_release_attr('version_id'),
739 self._parse_distro_release_content(
740 self.os_release_attr('pretty_name')).get('version_id', ''),
741 self._parse_distro_release_content(
742 self.lsb_release_attr('description')).get('version_id', ''),
743 self.uname_attr('release')
744 ]
745 version = ''
746 if best:
747 # This algorithm uses the last version in priority order that has
748 # the best precision. If the versions are not in conflict, that
749 # does not matter; otherwise, using the last one instead of the
750 # first one might be considered a surprise.
751 for v in versions:
752 if v.count(".") > version.count(".") or version == '':
753 version = v
754 else:
755 for v in versions:
756 if v != '':
757 version = v
758 break
759 if pretty and version and self.codename():
760 version = u'{0} ({1})'.format(version, self.codename())
761 return version
762
763 def version_parts(self, best=False):
764 """
765 Return the version of the OS distribution, as a tuple of version
766 numbers.
767
768 For details, see :func:`distro.version_parts`.
769 """
770 version_str = self.version(best=best)
771 if version_str:
772 version_regex = re.compile(r'(\d+)\.?(\d+)?\.?(\d+)?')
773 matches = version_regex.match(version_str)
774 if matches:
775 major, minor, build_number = matches.groups()
776 return major, minor or '', build_number or ''
777 return '', '', ''
778
779 def major_version(self, best=False):
780 """
781 Return the major version number of the current distribution.
782
783 For details, see :func:`distro.major_version`.
784 """
785 return self.version_parts(best)[0]
786
787 def minor_version(self, best=False):
788 """
789 Return the minor version number of the current distribution.
790
791 For details, see :func:`distro.minor_version`.
792 """
793 return self.version_parts(best)[1]
794
795 def build_number(self, best=False):
796 """
797 Return the build number of the current distribution.
798
799 For details, see :func:`distro.build_number`.
800 """
801 return self.version_parts(best)[2]
802
803 def like(self):
804 """
805 Return the IDs of distributions that are like the OS distribution.
806
807 For details, see :func:`distro.like`.
808 """
809 return self.os_release_attr('id_like') or ''
810
811 def codename(self):
812 """
813 Return the codename of the OS distribution.
814
815 For details, see :func:`distro.codename`.
816 """
817 try:
818 # Handle os_release specially since distros might purposefully set
819 # this to empty string to have no codename
820 return self._os_release_info['codename']
821 except KeyError:
822 return self.lsb_release_attr('codename') \
823 or self.distro_release_attr('codename') \
824 or ''
825
826 def info(self, pretty=False, best=False):
827 """
828 Return certain machine-readable information about the OS
829 distribution.
830
831 For details, see :func:`distro.info`.
832 """
833 return dict(
834 id=self.id(),
835 version=self.version(pretty, best),
836 version_parts=dict(
837 major=self.major_version(best),
838 minor=self.minor_version(best),
839 build_number=self.build_number(best)
840 ),
841 like=self.like(),
842 codename=self.codename(),
843 )
844
845 def os_release_info(self):
846 """
847 Return a dictionary containing key-value pairs for the information
848 items from the os-release file data source of the OS distribution.
849
850 For details, see :func:`distro.os_release_info`.
851 """
852 return self._os_release_info
853
854 def lsb_release_info(self):
855 """
856 Return a dictionary containing key-value pairs for the information
857 items from the lsb_release command data source of the OS
858 distribution.
859
860 For details, see :func:`distro.lsb_release_info`.
861 """
862 return self._lsb_release_info
863
864 def distro_release_info(self):
865 """
866 Return a dictionary containing key-value pairs for the information
867 items from the distro release file data source of the OS
868 distribution.
869
870 For details, see :func:`distro.distro_release_info`.
871 """
872 return self._distro_release_info
873
874 def uname_info(self):
875 """
876 Return a dictionary containing key-value pairs for the information
877 items from the uname command data source of the OS distribution.
878
879 For details, see :func:`distro.uname_info`.
880 """
881 return self._uname_info
882
883 def os_release_attr(self, attribute):
884 """
885 Return a single named information item from the os-release file data
886 source of the OS distribution.
887
888 For details, see :func:`distro.os_release_attr`.
889 """
890 return self._os_release_info.get(attribute, '')
891
892 def lsb_release_attr(self, attribute):
893 """
894 Return a single named information item from the lsb_release command
895 output data source of the OS distribution.
896
897 For details, see :func:`distro.lsb_release_attr`.
898 """
899 return self._lsb_release_info.get(attribute, '')
900
901 def distro_release_attr(self, attribute):
902 """
903 Return a single named information item from the distro release file
904 data source of the OS distribution.
905
906 For details, see :func:`distro.distro_release_attr`.
907 """
908 return self._distro_release_info.get(attribute, '')
909
910 def uname_attr(self, attribute):
911 """
912 Return a single named information item from the uname command
913 output data source of the OS distribution.
914
915 For details, see :func:`distro.uname_release_attr`.
916 """
917 return self._uname_info.get(attribute, '')
918
919 @cached_property
920 def _os_release_info(self):
921 """
922 Get the information items from the specified os-release file.
923
924 Returns:
925 A dictionary containing all information items.
926 """
927 if os.path.isfile(self.os_release_file):
928 with open(self.os_release_file) as release_file:
929 return self._parse_os_release_content(release_file)
930 return {}
931
932 @staticmethod
933 def _parse_os_release_content(lines):
934 """
935 Parse the lines of an os-release file.
936
937 Parameters:
938
939 * lines: Iterable through the lines in the os-release file.
940 Each line must be a unicode string or a UTF-8 encoded byte
941 string.
942
943 Returns:
944 A dictionary containing all information items.
945 """
946 props = {}
947 lexer = shlex.shlex(lines, posix=True)
948 lexer.whitespace_split = True
949
950 # The shlex module defines its `wordchars` variable using literals,
951 # making it dependent on the encoding of the Python source file.
952 # In Python 2.6 and 2.7, the shlex source file is encoded in
953 # 'iso-8859-1', and the `wordchars` variable is defined as a byte
954 # string. This causes a UnicodeDecodeError to be raised when the
955 # parsed content is a unicode object. The following fix resolves that
956 # (... but it should be fixed in shlex...):
957 if sys.version_info[0] == 2 and isinstance(lexer.wordchars, bytes):
958 lexer.wordchars = lexer.wordchars.decode('iso-8859-1')
959
960 tokens = list(lexer)
961 for token in tokens:
962 # At this point, all shell-like parsing has been done (i.e.
963 # comments processed, quotes and backslash escape sequences
964 # processed, multi-line values assembled, trailing newlines
965 # stripped, etc.), so the tokens are now either:
966 # * variable assignments: var=value
967 # * commands or their arguments (not allowed in os-release)
968 if '=' in token:
969 k, v = token.split('=', 1)
970 if isinstance(v, bytes):
971 v = v.decode('utf-8')
972 props[k.lower()] = v
973 else:
974 # Ignore any tokens that are not variable assignments
975 pass
976
977 if 'version_codename' in props:
978 # os-release added a version_codename field. Use that in
979 # preference to anything else Note that some distros purposefully
980 # do not have code names. They should be setting
981 # version_codename=""
982 props['codename'] = props['version_codename']
983 elif 'ubuntu_codename' in props:
984 # Same as above but a non-standard field name used on older Ubuntus
985 props['codename'] = props['ubuntu_codename']
986 elif 'version' in props:
987 # If there is no version_codename, parse it from the version
988 codename = re.search(r'(\(\D+\))|,(\s+)?\D+', props['version'])
989 if codename:
990 codename = codename.group()
991 codename = codename.strip('()')
992 codename = codename.strip(',')
993 codename = codename.strip()
994 # codename appears within paranthese.
995 props['codename'] = codename
996
997 return props
998
999 @cached_property
1000 def _lsb_release_info(self):
1001 """
1002 Get the information items from the lsb_release command output.
1003
1004 Returns:
1005 A dictionary containing all information items.
1006 """
1007 if not self.include_lsb:
1008 return {}
1009 with open(os.devnull, 'w') as devnull:
1010 try:
1011 cmd = ('lsb_release', '-a')
1012 stdout = subprocess.check_output(cmd, stderr=devnull)
1013 except OSError: # Command not found
1014 return {}
1015 content = stdout.decode(sys.getfilesystemencoding()).splitlines()
1016 return self._parse_lsb_release_content(content)
1017
1018 @staticmethod
1019 def _parse_lsb_release_content(lines):
1020 """
1021 Parse the output of the lsb_release command.
1022
1023 Parameters:
1024
1025 * lines: Iterable through the lines of the lsb_release output.
1026 Each line must be a unicode string or a UTF-8 encoded byte
1027 string.
1028
1029 Returns:
1030 A dictionary containing all information items.
1031 """
1032 props = {}
1033 for line in lines:
1034 kv = line.strip('\n').split(':', 1)
1035 if len(kv) != 2:
1036 # Ignore lines without colon.
1037 continue
1038 k, v = kv
1039 props.update({k.replace(' ', '_').lower(): v.strip()})
1040 return props
1041
1042 @cached_property
1043 def _uname_info(self):
1044 with open(os.devnull, 'w') as devnull:
1045 try:
1046 cmd = ('uname', '-rs')
1047 stdout = subprocess.check_output(cmd, stderr=devnull)
1048 except OSError:
1049 return {}
1050 content = stdout.decode(sys.getfilesystemencoding()).splitlines()
1051 return self._parse_uname_content(content)
1052
1053 @staticmethod
1054 def _parse_uname_content(lines):
1055 props = {}
1056 match = re.search(r'^([^\s]+)\s+([\d\.]+)', lines[0].strip())
1057 if match:
1058 name, version = match.groups()
1059
1060 # This is to prevent the Linux kernel version from
1061 # appearing as the 'best' version on otherwise
1062 # identifiable distributions.
1063 if name == 'Linux':
1064 return {}
1065 props['id'] = name.lower()
1066 props['name'] = name
1067 props['release'] = version
1068 return props
1069
1070 @cached_property
1071 def _distro_release_info(self):
1072 """
1073 Get the information items from the specified distro release file.
1074
1075 Returns:
1076 A dictionary containing all information items.
1077 """
1078 if self.distro_release_file:
1079 # If it was specified, we use it and parse what we can, even if
1080 # its file name or content does not match the expected pattern.
1081 distro_info = self._parse_distro_release_file(
1082 self.distro_release_file)
1083 basename = os.path.basename(self.distro_release_file)
1084 # The file name pattern for user-specified distro release files
1085 # is somewhat more tolerant (compared to when searching for the
1086 # file), because we want to use what was specified as best as
1087 # possible.
1088 match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
1089 if 'name' in distro_info \
1090 and 'cloudlinux' in distro_info['name'].lower():
1091 distro_info['id'] = 'cloudlinux'
1092 elif match:
1093 distro_info['id'] = match.group(1)
1094 return distro_info
1095 else:
1096 try:
1097 basenames = os.listdir(_UNIXCONFDIR)
1098 # We sort for repeatability in cases where there are multiple
1099 # distro specific files; e.g. CentOS, Oracle, Enterprise all
1100 # containing `redhat-release` on top of their own.
1101 basenames.sort()
1102 except OSError:
1103 # This may occur when /etc is not readable but we can't be
1104 # sure about the *-release files. Check common entries of
1105 # /etc for information. If they turn out to not be there the
1106 # error is handled in `_parse_distro_release_file()`.
1107 basenames = ['SuSE-release',
1108 'arch-release',
1109 'base-release',
1110 'centos-release',
1111 'fedora-release',
1112 'gentoo-release',
1113 'mageia-release',
1114 'mandrake-release',
1115 'mandriva-release',
1116 'mandrivalinux-release',
1117 'manjaro-release',
1118 'oracle-release',
1119 'redhat-release',
1120 'sl-release',
1121 'slackware-version']
1122 for basename in basenames:
1123 if basename in _DISTRO_RELEASE_IGNORE_BASENAMES:
1124 continue
1125 match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
1126 if match:
1127 filepath = os.path.join(_UNIXCONFDIR, basename)
1128 distro_info = self._parse_distro_release_file(filepath)
1129 if 'name' in distro_info:
1130 # The name is always present if the pattern matches
1131 self.distro_release_file = filepath
1132 distro_info['id'] = match.group(1)
1133 if 'cloudlinux' in distro_info['name'].lower():
1134 distro_info['id'] = 'cloudlinux'
1135 return distro_info
1136 return {}
1137
1138 def _parse_distro_release_file(self, filepath):
1139 """
1140 Parse a distro release file.
1141
1142 Parameters:
1143
1144 * filepath: Path name of the distro release file.
1145
1146 Returns:
1147 A dictionary containing all information items.
1148 """
1149 try:
1150 with open(filepath) as fp:
1151 # Only parse the first line. For instance, on SLES there
1152 # are multiple lines. We don't want them...
1153 return self._parse_distro_release_content(fp.readline())
1154 except (OSError, IOError):
1155 # Ignore not being able to read a specific, seemingly version
1156 # related file.
1157 # See https://github.com/nir0s/distro/issues/162
1158 return {}
1159
1160 @staticmethod
1161 def _parse_distro_release_content(line):
1162 """
1163 Parse a line from a distro release file.
1164
1165 Parameters:
1166 * line: Line from the distro release file. Must be a unicode string
1167 or a UTF-8 encoded byte string.
1168
1169 Returns:
1170 A dictionary containing all information items.
1171 """
1172 if isinstance(line, bytes):
1173 line = line.decode('utf-8')
1174 matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match(
1175 line.strip()[::-1])
1176 distro_info = {}
1177 if matches:
1178 # regexp ensures non-None
1179 distro_info['name'] = matches.group(3)[::-1]
1180 if matches.group(2):
1181 distro_info['version_id'] = matches.group(2)[::-1]
1182 if matches.group(1):
1183 distro_info['codename'] = matches.group(1)[::-1]
1184 elif line:
1185 distro_info['name'] = line.strip()
1186 return distro_info
1187
1188
1189 _distro = LinuxDistribution()
1190
1191
1192 def main():
1193 logger = logging.getLogger(__name__)
1194 logger.setLevel(logging.DEBUG)
1195 logger.addHandler(logging.StreamHandler(sys.stdout))
1196
1197 parser = argparse.ArgumentParser(description="OS distro info tool")
1198 parser.add_argument(
1199 '--json',
1200 '-j',
1201 help="Output in machine readable format",
1202 action="store_true")
1203 args = parser.parse_args()
1204
1205 if args.json:
1206 logger.info(json.dumps(info(), indent=4, sort_keys=True))
1207 else:
1208 logger.info('Name: %s', name(pretty=True))
1209 distribution_version = version(pretty=True)
1210 logger.info('Version: %s', distribution_version)
1211 distribution_codename = codename()
1212 logger.info('Codename: %s', distribution_codename)
1213
1214
1215 if __name__ == '__main__':
1216 main()