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