Mercurial > repos > guerler > springsuite
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() |