├── .coveragerc ├── .github ├── dependabot.yml ├── stale.yml └── workflows │ ├── ci.yaml │ └── deploy.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── codecov.yml ├── dev-requirements.txt ├── docs ├── conf.py └── index.rst ├── pyproject.toml ├── query_local_distro.py ├── setup.cfg ├── src └── distro │ ├── __init__.py │ ├── __main__.py │ ├── distro.py │ └── py.typed ├── tests ├── __init__.py ├── resources │ ├── cli │ │ └── fedora30 │ │ │ └── etc │ │ │ ├── fedora-release │ │ │ ├── os-release │ │ │ ├── redhat-release │ │ │ └── system-release │ ├── distros │ │ ├── __shared__ │ │ │ └── bin │ │ │ │ └── lsb_release │ │ ├── aix72 │ │ │ └── bin │ │ │ │ ├── oslevel │ │ │ │ └── uname │ │ ├── altlinux10 │ │ │ └── etc │ │ │ │ ├── altlinux-release │ │ │ │ ├── fedora-release │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ └── system-release │ │ ├── amazon2014 │ │ │ └── etc │ │ │ │ └── system-release │ │ ├── amazon2016 │ │ │ └── etc │ │ │ │ ├── os-release │ │ │ │ └── system-release │ │ ├── arch │ │ │ ├── etc │ │ │ │ ├── arch-release │ │ │ │ └── os-release │ │ │ └── usr │ │ │ │ └── lib │ │ │ │ └── os-release │ │ ├── armbian │ │ │ ├── etc │ │ │ │ ├── armbian-release │ │ │ │ └── os-release │ │ │ └── usr │ │ │ │ └── lib │ │ │ │ └── os-release │ │ ├── bttcb1 │ │ │ ├── etc │ │ │ │ ├── board-release │ │ │ │ └── os-release │ │ │ └── usr │ │ │ │ └── lib │ │ │ │ └── os-release │ │ ├── buildroot │ │ │ └── etc │ │ │ │ └── os-release │ │ ├── centos5 │ │ │ └── etc │ │ │ │ ├── centos-release │ │ │ │ ├── redhat-release │ │ │ │ └── system-release │ │ ├── centos7 │ │ │ └── etc │ │ │ │ ├── centos-release │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ └── system-release │ │ ├── cloudlinux5 │ │ │ └── etc │ │ │ │ └── redhat-release │ │ ├── cloudlinux6 │ │ │ └── etc │ │ │ │ └── redhat-release │ │ ├── cloudlinux7 │ │ │ └── etc │ │ │ │ ├── os-release │ │ │ │ └── redhat-release │ │ ├── coreos │ │ │ └── etc │ │ │ │ ├── oem-release │ │ │ │ └── os-release │ │ ├── debian10 │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ └── etc │ │ │ │ ├── debian_version │ │ │ │ └── os-release │ │ ├── debian8 │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ └── etc │ │ │ │ ├── debian_version │ │ │ │ └── os-release │ │ ├── debiantesting │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ └── etc │ │ │ │ ├── debian_version │ │ │ │ └── os-release │ │ ├── exherbo │ │ │ └── etc │ │ │ │ └── os-release │ │ ├── fedora19 │ │ │ └── etc │ │ │ │ ├── fedora-release │ │ │ │ ├── issue │ │ │ │ ├── issue.net │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ ├── system-release │ │ │ │ └── system-release-cpe │ │ ├── fedora23 │ │ │ ├── etc │ │ │ │ ├── fedora-release │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ └── system-release │ │ │ └── usr │ │ │ │ └── lib │ │ │ │ └── os-release │ │ ├── fedora30 │ │ │ ├── etc │ │ │ │ ├── fedora-release │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ └── system-release │ │ │ └── usr │ │ │ │ └── lib │ │ │ │ └── os-release │ │ ├── freebsd111 │ │ │ └── bin │ │ │ │ └── uname │ │ ├── gentoo │ │ │ └── etc │ │ │ │ ├── gentoo-release │ │ │ │ └── os-release │ │ ├── guix │ │ │ └── etc │ │ │ │ └── os-release │ │ ├── kali │ │ │ └── etc │ │ │ │ └── os-release │ │ ├── kvmibm1 │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ └── etc │ │ │ │ ├── base-release │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ └── system-release │ │ ├── linuxmint17 │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ └── etc │ │ │ │ ├── debian_version │ │ │ │ ├── lsb-release │ │ │ │ ├── os-release │ │ │ │ └── upstream-release │ │ │ │ └── lsb-release │ │ ├── mageia5 │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ ├── etc │ │ │ │ ├── lsb-release │ │ │ │ ├── mageia-release │ │ │ │ ├── mandrake-release │ │ │ │ ├── mandrakelinux-release │ │ │ │ ├── mandriva-release │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ ├── release │ │ │ │ └── version │ │ │ └── usr │ │ │ │ └── lib │ │ │ │ └── os-release │ │ ├── mandriva2011 │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ └── etc │ │ │ │ ├── lsb-release │ │ │ │ ├── mandrake-release │ │ │ │ ├── mandrakelinux-release │ │ │ │ ├── mandriva-release │ │ │ │ ├── redhat-release │ │ │ │ ├── release │ │ │ │ └── version │ │ ├── manjaro1512 │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ └── etc │ │ │ │ ├── lsb-release │ │ │ │ ├── manjaro-release │ │ │ │ └── os-release │ │ ├── midnightbsd12 │ │ │ └── bin │ │ │ │ └── uname │ │ ├── netbsd711 │ │ │ └── bin │ │ │ │ └── uname │ │ ├── openbsd62 │ │ │ └── bin │ │ │ │ └── uname │ │ ├── openelec6 │ │ │ └── etc │ │ │ │ └── os-release │ │ ├── opensuse15 │ │ │ └── etc │ │ │ │ └── os-release │ │ ├── opensuse42 │ │ │ └── etc │ │ │ │ ├── SuSE-release │ │ │ │ └── os-release │ │ ├── oracle7 │ │ │ └── etc │ │ │ │ ├── oracle-release │ │ │ │ └── os-release │ │ ├── raspbian7 │ │ │ └── etc │ │ │ │ ├── debian_version │ │ │ │ ├── os-release │ │ │ │ └── os-release.orig │ │ ├── raspbian8 │ │ │ └── etc │ │ │ │ ├── debian_version │ │ │ │ └── os-release │ │ ├── rhel5 │ │ │ └── etc │ │ │ │ └── redhat-release │ │ ├── rhel6 │ │ │ └── etc │ │ │ │ ├── redhat-release │ │ │ │ └── system-release │ │ ├── rhel7 │ │ │ └── etc │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ └── system-release │ │ ├── rocky │ │ │ ├── etc │ │ │ │ ├── centos-release │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ ├── rocky-release │ │ │ │ ├── rocky-release-upstream │ │ │ │ ├── system-release │ │ │ │ └── system-release-cpe │ │ │ └── usr │ │ │ │ └── lib │ │ │ │ └── os-release │ │ ├── scientific6 │ │ │ └── etc │ │ │ │ ├── redhat-release │ │ │ │ └── system-release │ │ ├── scientific7 │ │ │ └── etc │ │ │ │ ├── os-release │ │ │ │ ├── redhat-release │ │ │ │ ├── sl-release │ │ │ │ └── system-release │ │ ├── slackware14 │ │ │ └── etc │ │ │ │ ├── os-release │ │ │ │ └── slackware-version │ │ ├── sles12 │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ └── etc │ │ │ │ ├── SuSE-release │ │ │ │ └── os-release │ │ ├── ubuntu14 │ │ │ ├── bin │ │ │ │ └── lsb_release │ │ │ └── etc │ │ │ │ ├── debian_version │ │ │ │ ├── lsb-release │ │ │ │ └── os-release │ │ └── ubuntu16 │ │ │ ├── bin │ │ │ └── lsb_release │ │ │ └── etc │ │ │ ├── debian_version │ │ │ ├── lsb-release │ │ │ └── os-release │ ├── special │ │ └── empty-release │ └── testdistros │ │ ├── distro │ │ ├── baduname │ │ │ └── bin │ │ │ │ └── uname │ │ ├── dontincludeuname │ │ │ └── bin │ │ │ │ └── uname │ │ ├── emptyuname │ │ │ └── bin │ │ │ │ └── uname │ │ ├── unknowndistro │ │ │ └── etc │ │ │ │ └── unknowndistro-release │ │ └── usrlibosreleaseonly │ │ │ └── usr │ │ │ └── lib │ │ │ └── os-release │ │ └── lsb │ │ ├── lsb_rc001 │ │ └── bin │ │ │ └── lsb_release │ │ ├── lsb_rc002 │ │ └── bin │ │ │ └── lsb_release │ │ ├── lsb_rc126 │ │ └── bin │ │ │ └── lsb_release │ │ ├── lsb_rc130 │ │ └── bin │ │ │ └── lsb_release │ │ ├── lsb_rc255 │ │ └── bin │ │ │ └── lsb_release │ │ ├── ubuntu14_nomodules │ │ └── bin │ │ │ └── lsb_release │ │ ├── ubuntu14_normal │ │ └── bin │ │ │ └── lsb_release │ │ └── ubuntu14_trailingblanks │ │ └── bin │ │ └── lsb_release └── test_distro.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = 4 | distro.py 5 | 6 | [paths] 7 | source = 8 | distro.py 9 | .tox/*/lib/python*/site-packages/distro.py 10 | .tox/pypy/site-packages/distro.py 11 | 12 | [report] 13 | exclude_lines = 14 | if __name__ == .__main__.: 15 | raise ImportError 16 | raise subprocess.CalledProcessError -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "github-actions" 5 | commit-message: 6 | include: "scope" 7 | prefix: "Actions" 8 | directory: "/" 9 | labels: 10 | - "enhancement" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/probot/stale for defaults, many of which are not set here. 2 | issues: 3 | daysUntilStale: 360 4 | 5 | pulls: 6 | daysUntilStale: 60 7 | markComment: > 8 | This pull request has been automatically marked as stale because it has not had 9 | recent activity. It will be closed if no further activity occurs. Thank you 10 | for your contributions. 11 | daysUntilClose: 7 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | lint: 9 | name: Lint 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v5 17 | with: 18 | python-version: 3.x 19 | 20 | - name: Install tox 21 | run: python -m pip install tox 22 | 23 | - name: Run tox 24 | run: tox -e lint 25 | 26 | test: 27 | name: Python ${{ matrix.python }} 28 | runs-on: ${{ matrix.os }} 29 | 30 | strategy: 31 | matrix: 32 | os: 33 | - "ubuntu-latest" 34 | python: 35 | - "3.7" 36 | - "3.8" 37 | - "3.9" 38 | - "3.10" 39 | - "3.11" 40 | - "3.12" 41 | include: 42 | - os: "ubuntu-20.04" 43 | python: "3.6" 44 | - os: "ubuntu-20.04" 45 | python: "pypy-3.6" 46 | - os: "ubuntu-22.04" 47 | python: "pypy-3.10" 48 | - os: "macos-12" 49 | python: 3.12 50 | 51 | steps: 52 | - uses: actions/checkout@v4 53 | 54 | - name: Set up Python ${{ matrix.python }} 55 | uses: actions/setup-python@v5 56 | with: 57 | python-version: ${{ matrix.python }} 58 | 59 | - name: Install tox 60 | run: python -m pip install tox 61 | 62 | - name: Run tox 63 | run: tox -e py 64 | 65 | - name: Upload coverage to Codecov 66 | uses: codecov/codecov-action@v3 67 | with: 68 | files: .coverage 69 | 70 | windows: 71 | name: Windows 72 | runs-on: windows-latest 73 | 74 | steps: 75 | - uses: actions/checkout@v4 76 | 77 | - uses: codecov/codecov-action@v3 78 | 79 | - name: Set up Python 80 | uses: actions/setup-python@v5 81 | with: 82 | python-version: 3.x 83 | 84 | - name: Install tox 85 | run: python -m pip install tox 86 | 87 | - name: Run tox 88 | run: tox -e py 89 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: "Deploy to PyPI" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | tag: 6 | description: "Git tag to deploy to PyPI" 7 | required: true 8 | 9 | jobs: 10 | deploy: 11 | name: "Deploy to PyPI" 12 | runs-on: ubuntu-latest 13 | environment: Deploy 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | ref: ${{ github.event.inputs.tag }} 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: 3.x 21 | - shell: bash 22 | run: | 23 | python -m pip install --disable-pip-version-check -U pip 24 | python -m pip install -U build twine 25 | python -m build 26 | python -m twine check dist/* 27 | python -m twine upload --username=__token__ --password=${{ secrets.PYPI_TOKEN }} dist/* 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | /env/ 11 | /bin/ 12 | /build/ 13 | /develop-eggs/ 14 | /dist/ 15 | /eggs/ 16 | /lib/ 17 | /lib64/ 18 | /parts/ 19 | /sdist/ 20 | /var/ 21 | /*.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | src/*.egg-info/ 25 | 26 | # Installer logs 27 | pip-log.txt 28 | pip-delete-this-directory.txt 29 | 30 | # Unit test / coverage reports 31 | /htmlcov/ 32 | /.tox/ 33 | .coverage 34 | .cache 35 | nosetests.xml 36 | coverage.xml 37 | 38 | # Translations 39 | *.mo 40 | 41 | # Mr Developer 42 | .mr.developer.cfg 43 | .project 44 | .pydevproject 45 | 46 | # Rope 47 | .ropeproject 48 | 49 | # Django stuff: 50 | *.log 51 | *.pot 52 | 53 | # Sphinx documentation 54 | /docs/_build/ 55 | 56 | *.iml 57 | 58 | *COMMIT_MSG 59 | 60 | # QuickBuild 61 | .qbcache/ 62 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 22.3.0 4 | hooks: 5 | - id: black 6 | args: ["--target-version", "py36"] 7 | - repo: https://github.com/PyCQA/flake8 8 | rev: 6.1.0 9 | hooks: 10 | - id: flake8 11 | - repo: https://github.com/pycqa/isort 12 | rev: 5.12.0 13 | hooks: 14 | - id: isort 15 | - repo: https://github.com/pre-commit/mirrors-mypy 16 | rev: v0.910 17 | hooks: 18 | - id: mypy 19 | args: ["--strict"] 20 | additional_dependencies: [ 21 | "pytest==6.2.5", 22 | ] 23 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.12" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | python: 12 | install: 13 | - method: pip 14 | path: . -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.9.0 (2023.12.19) 2 | 3 | ENHANCEMENTS: 4 | * Refactor distro.info() method to return an InfoDict [[#360](https://github.com/python-distro/distro/pull/360)] 5 | * Ignore the file '/etc/board-release' [[#353](https://github.com/python-distro/distro/pull/353)] 6 | * Ignore the file '/etc/ec2_version' [[#359](https://github.com/python-distro/distro/pull/353)] 7 | 8 | RELEASE: 9 | * Run Python 3.6 on Ubuntu 20.04 for CI and bump isort [[#355](https://github.com/python-distro/distro/pull/355)] 10 | 11 | TESTS: 12 | * Test on modern versions of CPython and PyPy and macOS [[#362](https://github.com/python-distro/distro/pull/362)] 13 | * Add support for ALT Linux Server 10.1 distribution [[#354](https://github.com/python-distro/distro/pull/354)] 14 | * Add Debian Testing to the tests [[#356](https://github.com/python-distro/distro/pull/356)] 15 | * Update archlinux resource for tests [[#352](https://github.com/python-distro/distro/pull/352)] 16 | 17 | ## 1.8.0 (2022.10.10) 18 | 19 | BACKWARD COMPATIBILITY: 20 | * Replace `setup.py` with `build` [[#342](https://github.com/python-distro/distro/pull/342)] 21 | 22 | ENHANCEMENTS: 23 | * Lowered `LinuxDistribution._distro_release_info` method complexity [[#327](https://github.com/python-distro/distro/pull/327)] 24 | * Added official support for Buildroot distribution [[#329](https://github.com/python-distro/distro/pull/329)] 25 | * Added official support for Guix distribution [[#330](https://github.com/python-distro/distro/pull/330)] 26 | * Added support for `/etc/debian_version` [[#333](https://github.com/python-distro/distro/pull/333)] & [[#349](https://github.com/python-distro/distro/pull/349)] 27 | * Fixed a typography in CONTRIBUTING.md [[#340](https://github.com/python-distro/distro/pull/340)] 28 | * Improved README.md "Usage" code block [[#343](https://github.com/python-distro/distro/pull/343)] 29 | 30 | RELEASE: 31 | * Bumped black to v22.3.0 in pre-commit.ci configuration [[#331](https://github.com/python-distro/distro/pull/331)] 32 | * Enabled GitHub Dependabot to keep GitHub Actions up to date [[#335](https://github.com/python-distro/distro/pull/335)] 33 | 34 | ## 1.7.0 (2022.02.15) 35 | 36 | BACKWARD COMPATIBILITY: 37 | * Dropped support for EOL Pythons 2.7, 3.4 and 3.5 [[#281](https://github.com/python-distro/distro/pull/281)] 38 | * Dropped support for LSB and `uname` back-ends when `--root-dir` is specified [[#311](https://github.com/python-distro/distro/pull/311)] 39 | * Moved `distro.py` to `src/distro/distro.py` [[#315](https://github.com/python-distro/distro/pull/315)] 40 | 41 | ENHANCEMENTS: 42 | * Documented that `distro.version()` can return an empty string on rolling releases [[#312](https://github.com/python-distro/distro/pull/312)] 43 | * Documented support for Python 3.10 [[#316](https://github.com/python-distro/distro/pull/316)] 44 | * Added official support for Rocky Linux distribution [[#318](https://github.com/python-distro/distro/pull/318)] 45 | * Added a shebang to `distro.py` to allow standalone execution [[#313](https://github.com/python-distro/distro/pull/313)] 46 | * Added support for AIX platforms [[#311](https://github.com/python-distro/distro/pull/311)] 47 | * Added compliance for PEP-561 [[#315](https://github.com/python-distro/distro/pull/315)] 48 | 49 | BUG FIXES: 50 | * Fixed `include_uname` parameter oversight [[#305](https://github.com/python-distro/distro/pull/305)] 51 | * Fixed crash when `uname -rs` output is empty [[#304](https://github.com/python-distro/distro/pull/304)] 52 | * Fixed Amazon Linux identifier in `distro.id()` documentation [[#318](https://github.com/python-distro/distro/pull/318)] 53 | * Fixed OpenSuse >= 15 support [[#319](https://github.com/python-distro/distro/pull/319)] 54 | * Fixed encoding issues when opening distro release files [[#324](https://github.com/python-distro/distro/pull/324)] 55 | * Fixed `linux_distribution` regression introduced in [[#230](https://github.com/python-distro/distro/pull/230)] [[#325](https://github.com/python-distro/distro/pull/325)] 56 | 57 | ## 1.6.0 (2021.7.30) 58 | 59 | BACKWARDS COMPATIBILITY: 60 | * Deprecated the `distro.linux_distribution()` function. Use 61 | `distro.id()`, `distro.version()` and `distro.name()` instead 62 | [[#296](https://github.com/python-distro/distro/pull/296)] 63 | * Deprecated Python 2.7, 3.4 and 3.5 support. Further releases 64 | will only support Python 3.6+ 65 | 66 | ENHANCEMENTS: 67 | * Added type hints to `distro` module [[#269](https://github.com/python-distro/distro/pull/269)] 68 | * Added `__version__` for checking `distro` version [[#292](https://github.com/python-distro/distro/pull/292)] 69 | * Added support for arbitrary rootfs via the `root_dir` parameter [[#247](https://github.com/python-distro/distro/pull/247)] 70 | * Added the `--root-dir` option to CLI [[#161](https://github.com/python-distro/distro/issues/161)] 71 | * Added fallback to `/usr/lib/os-release` when `/etc/os-release` isn't available [[#262](https://github.com/python-distro/distro/pull/262)] 72 | 73 | BUG FIXES: 74 | * Fixed `subprocess.CalledProcessError` when running `lsb_release` [[#261](https://github.com/python-distro/distro/pull/261)] 75 | * Ignore `/etc/iredmail-release` file while parsing distribution [[#268](https://github.com/python-distro/distro/pull/268)] 76 | * Use a binary file for `/dev/null` to avoid `TextIOWrapper` overhead [[#271](https://github.com/python-distro/distro/pull/271)] 77 | 78 | RELEASE: 79 | * Moved repository from `nir0s/distro` to 80 | [`python-distro/distro`](https://github.com/python-distro/distro) on GitHub. 81 | 82 | ## 1.5.0 (2020.3.30) 83 | 84 | BACKWARD COMPATIBILITY: 85 | * Keep output as native string so we can compatible with python2 interface [[#248](https://github.com/python-distro/distro/pull/248)] 86 | 87 | ENHANCEMENTS: 88 | 89 | BUG FIXES: 90 | * Fix detection of RHEL 6 ComputeNode [[#255](https://github.com/python-distro/distro/issues/255)] 91 | * Fix Oracle 4/5 lsb_release id and names [[#250](https://github.com/python-distro/distro/pull/250)] 92 | * Ignore /etc/plesk-release file while parsing distribution [[#246](https://github.com/python-distro/distro/pull/246)] 93 | 94 | TESTS: 95 | * Test on MidnightBSD [[#254](https://github.com/python-distro/distro/issues/254)] 96 | 97 | RELEASE: 98 | * Remove build testing on Python 3.3 and add pypy 3.5 and Python 3.7 and 3.8 [[#244](https://github.com/python-distro/distro/pull/244)] 99 | * Fix Windows build due to Appveyor not supporting builds on Python 2.7 anymore [[#257](https://github.com/python-distro/distro/pull/257)] 100 | 101 | Additionally, The Python2.6 branch was fixed to support distro version 1.4.0. It is now passing all tests. Thanks [abadger](https://github.com/abadger)! 102 | 103 | ## 1.4.0 (2019.2.4) 104 | 105 | BACKWARD COMPATIBILITY: 106 | * Prefer the VERSION_CODENAME field of os-release to parsing it from VERSION [[#230](https://github.com/python-distro/distro/pull/230)] 107 | 108 | BUG FIXES: 109 | * Return _uname_info from the uname_info() method [[#233](https://github.com/python-distro/distro/pull/233)] 110 | * Fixed CloudLinux id discovery [[#234](https://github.com/python-distro/distro/pull/234)] 111 | * Update Oracle matching [[#224](https://github.com/python-distro/distro/pull/224)] 112 | 113 | DOCS: 114 | * Update Fedora package link [[#225](https://github.com/python-distro/distro/pull/225)] 115 | * Distro is the recommended replacement for platform.linux_distribution [[#220](https://github.com/python-distro/distro/pull/220)] 116 | 117 | RELEASE: 118 | * Use Markdown for long description in setup.py [[#219](https://github.com/python-distro/distro/pull/219)] 119 | 120 | Additionally, The Python2.6 branch was fixed and rebased on top of master. It is now passing all tests. Thanks [abadger](https://github.com/abadger)! 121 | 122 | ## 1.3.0 (2018.05.09) 123 | 124 | ENHANCEMENTS: 125 | * Added support for OpenBSD, FreeBSD, and NetBSD [[#207](https://github.com/python-distro/distro/issues/207)] 126 | 127 | TESTS: 128 | * Add test for Kali Linux Rolling [[#214](https://github.com/python-distro/distro/issues/214)] 129 | 130 | DOCS: 131 | * Update docs with regards to #207 [[#209](https://github.com/python-distro/distro/issues/209)] 132 | * Add Ansible reference implementation and fix arch-linux link [[#213](https://github.com/python-distro/distro/issues/213)] 133 | * Add facter reference implementation [[#213](https://github.com/python-distro/distro/issues/213)] 134 | 135 | ## 1.2.0 (2017.12.24) 136 | 137 | BACKWARD COMPATIBILITY: 138 | * Don't raise ImportError on non-linux platforms [[#202](https://github.com/python-distro/distro/issues/202)] 139 | 140 | ENHANCEMENTS: 141 | * Lazily load the LinuxDistribution data [[#201](https://github.com/python-distro/distro/issues/201)] 142 | 143 | BUG FIXES: 144 | * Stdout of shell should be decoded with sys.getfilesystemencoding() [[#203](https://github.com/python-distro/distro/issues/203)] 145 | 146 | TESTS: 147 | * Explicitly set Python versions on Travis for flake [[#204](https://github.com/python-distro/distro/issues/204)] 148 | 149 | 150 | ## 1.1.0 (2017.11.28) 151 | 152 | BACKWARD COMPATIBILITY: 153 | * Drop python3.3 support [[#199](https://github.com/python-distro/distro/issues/199)] 154 | * Remove Official Python26 support [[#195](https://github.com/python-distro/distro/issues/195)] 155 | 156 | TESTS: 157 | * Add MandrivaLinux test case [[#181](https://github.com/python-distro/distro/issues/181)] 158 | * Add test cases for CloudLinux 5, 6, and 7 [[#180](https://github.com/python-distro/distro/issues/180)] 159 | 160 | RELEASE: 161 | * Modify MANIFEST to include resources for tests and docs in source tarballs [[97c91a1](97c91a1)] 162 | 163 | ## 1.0.4 (2017.04.01) 164 | 165 | BUG FIXES: 166 | * Guess common \*-release files if /etc not readable [[#175](https://github.com/python-distro/distro/issues/175)] 167 | 168 | ## 1.0.3 (2017.03.19) 169 | 170 | ENHANCEMENTS: 171 | * Show keys for empty values when running distro from the CLI [[#160](https://github.com/python-distro/distro/issues/160)] 172 | * Add manual mapping for `redhatenterpriseserver` (previously only redhatenterpriseworkstation was mapped) [[#148](https://github.com/python-distro/distro/issues/148)] 173 | * Race condition in `_parse_distro_release_file` [[#163](https://github.com/python-distro/distro/issues/163)] 174 | 175 | TESTS: 176 | * Add RHEL5 test case [[#165](https://github.com/python-distro/distro/issues/165)] 177 | * Add OpenELEC test case [[#166](https://github.com/python-distro/distro/issues/166)] 178 | * Replace nose with pytest [[#158](https://github.com/python-distro/distro/issues/158)] 179 | 180 | RELEASE: 181 | * Update classifiers 182 | * Update supported Python versions (with py36) 183 | 184 | ## 1.0.2 (2017.01.12) 185 | 186 | TESTS: 187 | * Test on py33, py36 and py3 based flake8 188 | 189 | RELEASE: 190 | * Add MANIFEST file (which also includes the LICENSE as part of Issue [[#139](https://github.com/python-distro/distro/issues/139)]) 191 | * Default to releasing using Twine [[#121](https://github.com/python-distro/distro/issues/121)] 192 | * Add setup.cfg file [[#145](https://github.com/python-distro/distro/issues/145)] 193 | * Update license in setup.py 194 | 195 | ## 1.0.1 (2016-11-03) 196 | 197 | ENHANCEMENTS: 198 | * Prettify distro -j's output and add more elaborate docs [[#147](https://github.com/python-distro/distro/issues/147)] 199 | * Decode output of `lsb_release` as utf-8 [[#144](https://github.com/python-distro/distro/issues/144)] 200 | * Logger now uses `message %s, string` form to not-evaulate log messages if unnecessary [[#145](https://github.com/python-distro/distro/issues/145)] 201 | 202 | TESTS: 203 | * Increase code-coverage [[#146](https://github.com/python-distro/distro/issues/146)] 204 | * Fix landscape code-quality warnings [[#145](https://github.com/python-distro/distro/issues/145)] 205 | 206 | RELEASE: 207 | * Add CONTRIBUTING.md 208 | 209 | ## 1.0.0 (2016-09-25) 210 | 211 | BACKWARD COMPATIBILITY: 212 | * raise exception when importing on non-supported platforms [[#129](https://github.com/python-distro/distro/issues/129)] 213 | 214 | ENHANCEMENTS: 215 | * Use `bytes` invariantly [[#135](https://github.com/python-distro/distro/issues/135)] 216 | * Some minor code adjustments plus a CLI [[#134](https://github.com/python-distro/distro/issues/134)] 217 | * Emit stderr if `lsb_release` fails 218 | 219 | BUG FIXES: 220 | * Fix some encoding related issues 221 | 222 | TESTS: 223 | * Add many test cases (e.g. Raspbian 8, CoreOS, Amazon Linux, Scientific Linux, Gentoo, Manjaro) 224 | * Completely redo the testing framework to make it easier to add tests 225 | * Test on pypy 226 | 227 | RELEASE: 228 | * Remove six as a dependency 229 | 230 | ## 0.6.0 (2016-04-21) 231 | 232 | This is the first release of `distro`. 233 | All previous work was done on `ld` and therefore unmentioned here. See the release log in GitHub if you want the entire log. 234 | 235 | BACKWARD COMPATIBILITY: 236 | * No longer a package. constants.py has been removed and distro is now a single module 237 | 238 | ENHANCEMENTS: 239 | * distro.info() now receives best and pretty flags 240 | * Removed get_ prefix from get_*_release_attr functions 241 | * Codename is now passed in distro.info() 242 | 243 | TESTS: 244 | * Added Linux Mint test case 245 | * Now testing on Python 3.4 246 | 247 | DOCS: 248 | * Documentation fixes 249 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # General 2 | 3 | * Contributing to distro identification currently doesn't have any specific standards and rather depends on the specific implementation. 4 | * A 100% coverage is expected for each PR unless explicitly authorized by the reviewer. 5 | * Please try to maintain maximum code-health (via landscape.io). 6 | 7 | # Contributing distro specific tests 8 | 9 | Distro's tests are implemented via a standardized framework under `tests/test_distro.py` 10 | 11 | For each distribution, tests should be added in the relevant class according to which distribution file(s) exists on it, so, for example, tests should be added under `TestOSRelease` where `/etc/os-release` is available. 12 | 13 | The tests must be self-contained, meaning that the release files for the distribution should be maintained in the repository under `tests/resources/distros/distribution_name+distribution_version`. 14 | 15 | A tests method would like somewhat like this: 16 | 17 | ```python 18 | def test_centos7_os_release(self): 19 | desired_outcome = { 20 | 'id': 'centos', 21 | 'name': 'CentOS Linux', 22 | 'pretty_name': 'CentOS Linux 7 (Core)', 23 | 'version': '7', 24 | 'pretty_version': '7 (Core)', 25 | 'best_version': '7', 26 | 'like': 'rhel fedora', 27 | 'codename': 'Core' 28 | } 29 | self._test_outcome(desired_outcome) 30 | ``` 31 | 32 | The framework will automatically try to pick up the relevant file according to the method's name (`centos7` meaning the folder should be named `centos7` as well) and compare the `desired_outcome` with the parsed files found under the test dir. 33 | 34 | The exception to the rule is under the `TestDistroRelease` test class which should look somewhat like this: 35 | 36 | ```python 37 | def test_centos5_dist_release(self): 38 | desired_outcome = { 39 | 'id': 'centos', 40 | 'name': 'CentOS', 41 | 'pretty_name': 'CentOS 5.11 (Final)', 42 | 'version': '5.11', 43 | 'pretty_version': '5.11 (Final)', 44 | 'best_version': '5.11', 45 | 'codename': 'Final', 46 | 'major_version': '5', 47 | 'minor_version': '11' 48 | } 49 | self._test_outcome(desired_outcome, 'centos', '5') 50 | ``` 51 | 52 | Where the name of the method is not indicative of the lookup folder but rather the two last arguments in `_test_outcome`. 53 | 54 | A test case is mandatory under `TestOverall` for a PR to be complete. 55 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | We thank anyone who has contributed to this project, and welcome anyone to contribute further. 2 | 3 | For a list of contributors, please see https://github.com/python-distro/distro/graphs/contributors -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | include *.py 3 | include *.txt 4 | include LICENSE 5 | include CHANGES 6 | include Makefile 7 | 8 | graft tests 9 | 10 | include docs/* 11 | 12 | global-exclude *.py[co] 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2021 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 | # Name of this package 16 | PACKAGENAME = distro 17 | 18 | # You can set these variables from the command line, and also 19 | # from the environment for the first two. 20 | SPHINXOPTS ?= -n -v 21 | SPHINXBUILD ?= sphinx-build 22 | SPHINXSOURCEDIR = docs 23 | SPHINXBUILDDIR = docs/_build 24 | 25 | .PHONY: help 26 | help: 27 | @echo 'Please use "make " where is one of' 28 | @echo " release - build a release and publish it" 29 | @echo " dev - prepare a development environment (includes tests)" 30 | @echo " instdev - prepare a development environment (no tests)" 31 | @echo " install - install into current Python environment" 32 | @echo " clobber - remove any build products" 33 | @echo " build - build the package" 34 | @echo " test - test from this directory using tox, including test coverage" 35 | @echo " publish - upload to PyPI" 36 | @echo " clean - remove any temporary build products" 37 | @echo " dry-run - perform all action required for a release without actually releasing" 38 | @$(SPHINXBUILD) -M help "$(SPHINXSOURCEDIR)" "$(SPHINXBUILDDIR)" $(SPHINXOPTS) $(O) 39 | 40 | .PHONY: release 41 | release: test clean build publish 42 | @echo "$@ done." 43 | 44 | .PHONY: test 45 | test: 46 | pip install 'tox>=1.9.0' 47 | tox 48 | @echo "$@ done." 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf dist build $(PACKAGENAME).egg-info 53 | @echo "$@ done." 54 | 55 | .PHONY: build 56 | build: 57 | python -m build 58 | 59 | .PHONY: publish 60 | publish: 61 | twine upload -r pypi dist/$(PACKAGENAME)-* 62 | @echo "$@ done." 63 | 64 | .PHONY: dry-run 65 | dry-run: test clean build 66 | @echo "$@ done." 67 | 68 | .PHONY: dev 69 | dev: instdev test 70 | @echo "$@ done." 71 | 72 | .PHONY: instdev 73 | instdev: 74 | pip install -r dev-requirements.txt 75 | pip install -e . 76 | @echo "$@ done." 77 | 78 | .PHONY: install 79 | install: 80 | pip install . 81 | @echo "$@ done." 82 | 83 | .PHONY: clobber 84 | clobber: clean 85 | rm -rf $(SPHINXBUILDDIR) 86 | @echo "$@ done." 87 | 88 | # Catch-all target: route all unknown targets to Sphinx using the new 89 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 90 | .PHONY: Makefile 91 | %: Makefile 92 | @$(SPHINXBUILD) -M $@ "$(SPHINXSOURCEDIR)" "$(SPHINXBUILDDIR)" $(SPHINXOPTS) $(O) 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Distro - an OS platform information API 2 | ======================================= 3 | 4 | [![CI Status](https://github.com/python-distro/distro/workflows/CI/badge.svg)](https://github.com/python-distro/distro/actions/workflows/ci.yaml) 5 | [![PyPI version](http://img.shields.io/pypi/v/distro.svg)](https://pypi.python.org/pypi/distro) 6 | [![Supported Python Versions](https://img.shields.io/pypi/pyversions/distro.svg)](https://img.shields.io/pypi/pyversions/distro.svg) 7 | [![Code Coverage](https://codecov.io/github/python-distro/distro/coverage.svg?branch=master)](https://codecov.io/github/python-distro/distro?branch=master) 8 | [![Is Wheel](https://img.shields.io/pypi/wheel/distro.svg?style=flat)](https://pypi.python.org/pypi/distro) 9 | [![Latest Github Release](https://readthedocs.org/projects/distro/badge/?version=stable)](http://distro.readthedocs.io/en/latest/) 10 | 11 | `distro` provides information about the 12 | OS distribution it runs on, such as a reliable machine-readable ID, or 13 | version information. 14 | 15 | It is the recommended replacement for Python's original 16 | [`platform.linux_distribution`](https://docs.python.org/3.7/library/platform.html#platform.linux_distribution) 17 | function (removed in Python 3.8). It also provides much more functionality 18 | which isn't necessarily Python bound, like a command-line interface. 19 | 20 | Distro currently supports Linux and BSD based systems but [Windows and OS X support](https://github.com/python-distro/distro/issues/177) is also planned. 21 | 22 | For Python 2.6 support, see https://github.com/python-distro/distro/tree/python2.6-support 23 | 24 | ## Installation 25 | 26 | Installation of the latest released version from PyPI: 27 | 28 | ```shell 29 | pip install distro 30 | ``` 31 | 32 | Installation of the latest development version: 33 | 34 | ```shell 35 | pip install https://github.com/python-distro/distro/archive/master.tar.gz 36 | ``` 37 | 38 | To use as a standalone script, download `distro.py` directly: 39 | 40 | ```shell 41 | curl -O https://raw.githubusercontent.com/python-distro/distro/master/src/distro/distro.py 42 | python distro.py 43 | ``` 44 | 45 | ``distro`` is safe to vendor within projects that do not wish to add 46 | dependencies. 47 | 48 | ```shell 49 | cd myproject 50 | curl -O https://raw.githubusercontent.com/python-distro/distro/master/src/distro/distro.py 51 | ``` 52 | 53 | ## Usage 54 | 55 | ```bash 56 | $ distro 57 | Name: Antergos Linux 58 | Version: 2015.10 (ISO-Rolling) 59 | Codename: ISO-Rolling 60 | 61 | $ distro -j 62 | { 63 | "codename": "ISO-Rolling", 64 | "id": "antergos", 65 | "like": "arch", 66 | "version": "16.9", 67 | "version_parts": { 68 | "build_number": "", 69 | "major": "16", 70 | "minor": "9" 71 | } 72 | } 73 | 74 | 75 | $ python 76 | >>> import distro 77 | >>> distro.name(pretty=True) 78 | 'CentOS Linux 8' 79 | >>> distro.id() 80 | 'centos' 81 | >>> distro.version(best=True) 82 | '8.4.2105' 83 | ``` 84 | 85 | 86 | ## Documentation 87 | 88 | On top of the aforementioned API, several more functions are available. For a complete description of the 89 | API, see the [latest API documentation](http://distro.readthedocs.org/en/latest/). 90 | 91 | ## Background 92 | 93 | An alternative implementation became necessary because Python 3.5 deprecated 94 | this function, and Python 3.8 removed it altogether. Its predecessor function 95 | [`platform.dist`](https://docs.python.org/3.7/library/platform.html#platform.dist) 96 | was already deprecated since Python 2.6 and removed in Python 3.8. Still, there 97 | are many cases in which access to that information is needed. See [Python issue 98 | 1322](https://bugs.python.org/issue1322) for more information. 99 | 100 | The `distro` package implements a robust and inclusive way of retrieving the 101 | information about a distribution based on new standards and old methods, 102 | namely from these data sources (from high to low precedence): 103 | 104 | * The os-release file `/etc/os-release` if present, with a fall-back on `/usr/lib/os-release` if needed. 105 | * The output of the `lsb_release` command, if available. 106 | * The distro release file (`/etc/*(-|_)(release|version)`), if present. 107 | * The `uname` command for BSD based distrubtions. 108 | 109 | 110 | ## Python and Distribution Support 111 | 112 | `distro` is supported and tested on Python 3.6+ and PyPy and on any 113 | distribution that provides one or more of the data sources covered. 114 | 115 | This package is tested with test data that mimics the exact behavior of the data sources of [a number of Linux distributions](https://github.com/python-distro/distro/tree/master/tests/resources/distros). 116 | 117 | 118 | ## Testing 119 | 120 | ```shell 121 | git clone git@github.com:python-distro/distro.git 122 | cd distro 123 | pip install tox 124 | tox 125 | ``` 126 | 127 | 128 | ## Contributions 129 | 130 | Pull requests are always welcome to deal with specific distributions or just 131 | for general merriment. 132 | 133 | See [CONTRIBUTIONS](https://github.com/python-distro/distro/blob/master/CONTRIBUTING.md) for contribution info. 134 | 135 | Reference implementations for supporting additional distributions and file 136 | formats can be found here: 137 | 138 | * https://github.com/saltstack/salt/blob/develop/salt/grains/core.py#L1172 139 | * https://github.com/chef/ohai/blob/master/lib/ohai/plugins/linux/platform.rb 140 | * https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/facts/system/distribution.py 141 | * https://github.com/puppetlabs/facter/blob/master/lib/src/facts/linux/os_linux.cc 142 | 143 | ## Package manager distributions 144 | 145 | * https://src.fedoraproject.org/rpms/python-distro 146 | * https://www.archlinux.org/packages/community/any/python-distro/ 147 | * https://launchpad.net/ubuntu/+source/python-distro 148 | * https://packages.debian.org/stable/python3-distro 149 | * https://packages.gentoo.org/packages/dev-python/distro 150 | * https://pkgs.org/download/python3-distro 151 | * https://slackbuilds.org/repository/14.2/python/python-distro/ 152 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | coverage: 3 | status: 4 | patch: 5 | default: 6 | target: '97' 7 | project: 8 | default: 9 | target: '97' -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-cov 3 | sphinx>=1.1 4 | pre-commit==2.13.0; python_version > '3.5' 5 | black; python_version > '3.5' and platform_python_implementation != 'PyPy' 6 | mypy; python_version > '3.5' and platform_python_implementation != 'PyPy' 7 | build -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | import datetime 10 | import os 11 | import sys 12 | 13 | # If extensions (or modules to document with autodoc) are in another directory, 14 | # add these directories to sys.path here. If the directory is relative to the 15 | # documentation root, use os.path.abspath to make it absolute, like shown here. 16 | sys.path.insert(0, os.path.abspath("..")) 17 | 18 | import distro # noqa: E402 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = "distro" 23 | copyright = f"{datetime.date.today().year}, Nir Cohen, Andreas Maier" 24 | author = "Nir Cohen, Andreas Maier" 25 | 26 | # The short X.Y version. 27 | # Note: We use the full version in both cases. 28 | version = distro.__version__ # type: ignore 29 | 30 | # The full version, including alpha/beta/rc tags 31 | release = version 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # Add any Sphinx extension module names here, as strings. They can be 37 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 38 | # ones. 39 | extensions = [ 40 | "sphinx.ext.autodoc", 41 | "sphinx.ext.intersphinx", 42 | "sphinx.ext.coverage", 43 | "sphinx.ext.viewcode", 44 | ] 45 | 46 | # List of patterns, relative to source directory, that match files and 47 | # directories to ignore when looking for source files. 48 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 49 | 50 | 51 | # -- Options for HTML output ------------------------------------------------- 52 | 53 | # The theme to use for HTML and HTML Help pages. See 54 | # https://www.sphinx-doc.org/en/master/usage/theming.html or a list of builtin 55 | # themes. 56 | html_theme = "classic" 57 | 58 | 59 | # -- Options for intersphinx extension ------------------------------------ 60 | # For documentation, see 61 | # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html 62 | 63 | # Defines the prefixes for intersphinx links and the targets they resolve to. 64 | # Use Python 3.7 as that is the last version to include 65 | # platform.linux_distribution() and platform.dist(). Example RST source for 66 | # 'py' prefix: 67 | # :py:func:`platform.dist` 68 | intersphinx_mapping = {"py": ("https://docs.python.org/3.7", None)} 69 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _distro official repo: https://github.com/python-distro/distro 2 | .. _distro issue tracker: https://github.com/python-distro/distro/issues 3 | .. _open issues on missing test data: https://github.com/python-distro/distro/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22> 4 | 5 | 6 | **distro** package (Linux Distribution) version |version| 7 | ********************************************************* 8 | 9 | Official distro repository: `distro official repo`_ 10 | 11 | Overview and motivation 12 | ======================= 13 | 14 | .. automodule:: distro 15 | 16 | If you want to jump into the API description right away, read about the 17 | `consolidated accessor functions`_. 18 | 19 | Compatibility 20 | ============= 21 | 22 | The ``distro`` package is supported on Python 3.6+ and PyPy, and on any Linux 23 | or BSD distribution that provides one or more of the `data sources`_ used by 24 | this package. 25 | 26 | This package is tested on Python 3.6+ and PyPy, with test data that mimics the 27 | exact behavior of the data sources of `a number of Linux distributions 28 | `_. 29 | 30 | If you want to add test data for more distributions, please 31 | create an issue in the `distro issue tracker`_ 32 | and provide the following information in the issue: 33 | 34 | * The os-release file `/etc/os-release` if present, with a fall-back on `/usr/lib/os-release` if needed. 35 | * The file names and content of the `/etc/*release` and `/etc/*version` files, if any. 36 | * The output of the command: `lsb_release -a`, if available. 37 | * The file names and content of any other files you are aware of that provide 38 | useful information about the distro. 39 | 40 | There are already some `open issues on missing test data`_. 41 | 42 | 43 | Data sources 44 | ============ 45 | 46 | The ``distro`` package implements a robust and inclusive way of retrieving the 47 | information about a Linux distribution based on new standards and old methods, 48 | namely from these data sources: 49 | 50 | * The `os-release file`_, if present. 51 | 52 | * The `lsb_release command output`_, if the lsb_release command is available. 53 | 54 | * The `distro release file`_, if present. 55 | 56 | * The ``uname`` command output, if present. 57 | 58 | 59 | Access to the information 60 | ========================= 61 | 62 | This package provides three ways to access the information about a Linux 63 | distribution: 64 | 65 | * `Consolidated accessor functions`_ 66 | 67 | These are module-global functions that take into account all data sources in 68 | a priority order, and that return information about the current Linux 69 | distribution. 70 | 71 | These functions should be the normal way to access the information. 72 | 73 | The precedence of data sources is applied for each information item 74 | separately. Therefore, it is possible that not all information items returned 75 | by these functions come from the same data source. For example, on a 76 | distribution that has an lsb_release command that returns the 77 | "Distributor ID" field but not the "Codename" field, and that has a distro 78 | release file that specifies a codename inside, the distro ID will come from 79 | the lsb_release command (because it has higher precedence), and the codename 80 | will come from the distro release file (because it is not provided by the 81 | lsb_release command). 82 | 83 | Examples: :func:`distro.id` for retrieving the distro ID, or 84 | :func:`distro.info` to get the machine-readable part of the information in a 85 | more aggregated way, or :func:`distro.linux_distribution` with an interface 86 | that is compatible to the original :py:func:`platform.linux_distribution` 87 | function, supporting a subset of its parameters. 88 | 89 | * `Single source accessor functions`_ 90 | 91 | These are module-global functions that take into account a single data 92 | source, and that return information about the current Linux distribution. 93 | 94 | They are useful for distributions that provide multiple inconsistent data 95 | sources, or for retrieving information items that are not provided by the 96 | consolidated accessor functions. 97 | 98 | Examples: :func:`distro.os_release_attr` for retrieving a single information 99 | item from the os-release data source, or :func:`distro.lsb_release_info` for 100 | retrieving all information items from the lsb_release command output data 101 | source. 102 | 103 | * `LinuxDistribution class`_ 104 | 105 | The :class:`distro.LinuxDistribution` class provides the main code of this 106 | package. 107 | 108 | This package contains a private module-global :class:`distro.LinuxDistribution` 109 | instance with default initialization arguments, that is used by the 110 | consolidated and single source accessor functions. 111 | 112 | A user-defined instance of the :class:`distro.LinuxDistribution` class allows 113 | specifying the path names of the os-release file and distro release file and 114 | whether the lsb_release command should be used or not. That is useful for 115 | example when the distribution information from a chrooted environment 116 | is to be retrieved, or when a distro has multiple distro release files and 117 | the default algorithm uses the wrong one. 118 | 119 | 120 | Consolidated accessor functions 121 | =============================== 122 | 123 | This section describes the consolidated accessor functions. 124 | See `access to the information`_ for a discussion of the different kinds of 125 | accessor functions. 126 | 127 | .. autofunction:: distro.linux_distribution 128 | .. autofunction:: distro.id 129 | .. autofunction:: distro.name 130 | .. autofunction:: distro.version 131 | .. autofunction:: distro.version_parts 132 | .. autofunction:: distro.major_version 133 | .. autofunction:: distro.minor_version 134 | .. autofunction:: distro.build_number 135 | .. autofunction:: distro.like 136 | .. autofunction:: distro.codename 137 | .. autofunction:: distro.info 138 | 139 | Single source accessor functions 140 | ================================ 141 | 142 | This section describes the single source accessor functions. 143 | See `access to the information`_ for a discussion of the different kinds of 144 | accessor functions. 145 | 146 | .. autofunction:: distro.os_release_info 147 | .. autofunction:: distro.lsb_release_info 148 | .. autofunction:: distro.distro_release_info 149 | .. autofunction:: distro.uname_info 150 | .. autofunction:: distro.os_release_attr 151 | .. autofunction:: distro.lsb_release_attr 152 | .. autofunction:: distro.distro_release_attr 153 | .. autofunction:: distro.uname_attr 154 | 155 | LinuxDistribution class 156 | ======================= 157 | 158 | This section describes the access via the :class:`distro.LinuxDistribution` class. 159 | See `access to the information`_ for a discussion of the different kinds of 160 | accessor functions. 161 | 162 | .. autoclass:: distro.LinuxDistribution 163 | :members: 164 | :undoc-members: 165 | 166 | Normalization tables 167 | ==================== 168 | 169 | These translation tables are used to normalize the parsed distro ID values 170 | into reliable IDs. See :func:`distro.id` for details. 171 | 172 | They are documented in order to show for which distros a normalization is 173 | currently defined. 174 | 175 | As a quick fix, these tables can also be extended by the user by appending new 176 | entries, should the need arise. If you have a need to get these tables 177 | extended, please make an according request in the `distro issue tracker`_. 178 | 179 | .. autodata:: distro.NORMALIZED_OS_ID 180 | .. autodata:: distro.NORMALIZED_LSB_ID 181 | .. autodata:: distro.NORMALIZED_DISTRO_ID 182 | 183 | Os-release file 184 | =============== 185 | 186 | The os-release file is looked up using the path name ``/etc/os-release``. Its 187 | optional additional location ``/usr/lib/os-release`` would be looked up for if 188 | the former couldn't be read. 189 | 190 | The os-release file is expected to be encoded in UTF-8. 191 | 192 | It is parsed using the standard Python :py:mod:`shlex` package, which treats it 193 | like a shell script. 194 | 195 | The attribute names found in the file are translated to lower case and then 196 | become the keys of the information items from the os-release file data source. 197 | These keys can be used to retrieve single items with the 198 | :func:`distro.os_release_attr` function, and they are also used as keys in the 199 | dictionary returned by :func:`distro.os_release_info`. 200 | 201 | The attribute values found in the file are processed using shell rules (e.g. 202 | for whitespace, escaping, and quoting) before they become the values of the 203 | information items from the os-release file data source. 204 | 205 | If the attribute "VERSION" is found in the file, the distro codename is 206 | extracted from its value if it can be found there. If a codename is found, it 207 | becomes an additional information item with key "codename". 208 | 209 | See the `os-release man page 210 | `_ 211 | for a list of possible attributes in the file. 212 | 213 | **Examples:** 214 | 215 | 1. The following os-release file content: 216 | 217 | .. sourcecode:: shell 218 | 219 | NAME='Ubuntu' 220 | VERSION="14.04.3 LTS, Trusty Tahr" 221 | ID=ubuntu 222 | ID_LIKE=debian 223 | PRETTY_NAME="Ubuntu 14.04.3 LTS" 224 | VERSION_ID="14.04" 225 | HOME_URL="http://www.ubuntu.com/" 226 | SUPPORT_URL="http://help.ubuntu.com/" 227 | BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" 228 | 229 | results in these information items: 230 | 231 | =============================== ========================================== 232 | Key Value 233 | =============================== ========================================== 234 | name "Ubuntu" 235 | version "14.04.3 LTS, Trusty Tahr" 236 | id "ubuntu" 237 | id_like "debian" 238 | pretty_name "Ubuntu 14.04.3 LTS" 239 | version_id "14.04" 240 | home_url "http://www.ubuntu.com/" 241 | support_url "http://help.ubuntu.com/" 242 | bug_report_url "http://bugs.launchpad.net/ubuntu/" 243 | codename "Trusty Tahr" 244 | =============================== ========================================== 245 | 246 | 2. The following os-release file content: 247 | 248 | .. sourcecode:: shell 249 | 250 | NAME="Red Hat Enterprise Linux Server" 251 | VERSION="7.0 (Maipo)" 252 | ID="rhel" 253 | ID_LIKE="fedora" 254 | VERSION_ID="7.0" 255 | PRETTY_NAME="Red Hat Enterprise Linux Server 7.0 (Maipo)" 256 | ANSI_COLOR="0;31" 257 | CPE_NAME="cpe:/o:redhat:enterprise_linux:7.0:GA:server" 258 | HOME_URL="https://www.redhat.com/" 259 | BUG_REPORT_URL="https://bugzilla.redhat.com/" 260 | 261 | REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7" 262 | REDHAT_BUGZILLA_PRODUCT_VERSION=7.0 263 | REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux" 264 | REDHAT_SUPPORT_PRODUCT_VERSION=7.0 265 | 266 | results in these information items: 267 | 268 | =============================== ========================================== 269 | Key Value 270 | =============================== ========================================== 271 | name "Red Hat Enterprise Linux Server" 272 | version "7.0 (Maipo)" 273 | id "rhel" 274 | id_like "fedora" 275 | version_id "7.0" 276 | pretty_name "Red Hat Enterprise Linux Server 7.0 (Maipo)" 277 | ansi_color "0;31" 278 | cpe_name "cpe:/o:redhat:enterprise_linux:7.0:GA:server" 279 | home_url "https://www.redhat.com/" 280 | bug_report_url "https://bugzilla.redhat.com/" 281 | redhat_bugzilla_product "Red Hat Enterprise Linux 7" 282 | redhat_bugzilla_product_version "7.0" 283 | redhat_support_product "Red Hat Enterprise Linux" 284 | redhat_support_product_version "7.0" 285 | codename "Maipo" 286 | =============================== ========================================== 287 | 288 | Lsb_release command output 289 | ========================== 290 | 291 | The lsb_release command is expected to be in the PATH, and is invoked as 292 | follows: 293 | 294 | .. sourcecode:: shell 295 | 296 | lsb_release -a 297 | 298 | The command output is expected to be encoded in UTF-8. 299 | 300 | Only lines in the command output with the following format will be used: 301 | 302 | ``: `` 303 | 304 | Where: 305 | 306 | * ```` is the name of the attribute, and 307 | * ```` is the attribute value. 308 | 309 | The attribute names are stripped from surrounding blanks, any remaining blanks 310 | are translated to underscores, they are translated to lower case, and then 311 | become the keys of the information items from the lsb_release command output 312 | data source. 313 | 314 | The attribute values are stripped from surrounding blanks, and then become the 315 | values of the information items from the lsb_release command output data 316 | source. 317 | 318 | See the `lsb_release man page 319 | `_ 321 | for a description of standard attributes returned by the lsb_release command. 322 | 323 | **Examples:** 324 | 325 | 1. The following lsb_release command output: 326 | 327 | .. sourcecode:: text 328 | 329 | No LSB modules are available. 330 | Distributor ID: Ubuntu 331 | Description: Ubuntu 14.04.3 LTS 332 | Release: 14.04 333 | Codename: trusty 334 | 335 | results in these information items: 336 | 337 | =============================== ========================================== 338 | Key Value 339 | =============================== ========================================== 340 | distributor_id "Ubuntu" 341 | description "Ubuntu 14.04.3 LTS" 342 | release "14.04" 343 | codename "trusty" 344 | =============================== ========================================== 345 | 346 | 2. The following lsb_release command output: 347 | 348 | .. sourcecode:: text 349 | 350 | LSB Version: n/a 351 | Distributor ID: SUSE LINUX 352 | Description: SUSE Linux Enterprise Server 12 SP1 353 | Release: 12.1 354 | Codename: n/a 355 | 356 | results in these information items: 357 | 358 | =============================== ========================================== 359 | Key Value 360 | =============================== ========================================== 361 | lsb_version "n/a" 362 | distributor_id "SUSE LINUX" 363 | description "SUSE Linux Enterprise Server 12 SP1" 364 | release "12.1" 365 | codename "n/a" 366 | =============================== ========================================== 367 | 368 | Distro release file 369 | =================== 370 | 371 | Unless specified with a particular path name when using the 372 | :class:`distro.LinuxDistribution` class, the distro release file is found by using 373 | the first match in the alphabetically sorted list of the files matching the 374 | following path name patterns: 375 | 376 | * ``/etc/*-release`` 377 | * ``/etc/*_release`` 378 | * ``/etc/*-version`` 379 | * ``/etc/*_version`` 380 | 381 | where the following special path names are excluded: 382 | 383 | * ``/etc/debian_version`` 384 | * ``/etc/system-release`` 385 | * ``/etc/os-release`` 386 | 387 | and where the first line within the file has the expected format. 388 | 389 | The algorithm to sort the files alphabetically is far from perfect, but the 390 | distro release file has the least priority as a data source, and it is expected 391 | that distributions provide one of the other data sources. 392 | 393 | The distro release file is expected to be encoded in UTF-8. 394 | 395 | Only its first line is used, and it is expected to have the following format: 396 | 397 | `` [[[release] ] ()]`` 398 | 399 | Where: 400 | 401 | * square brackets indicate optionality, 402 | * ```` is the distro name, 403 | * ```` is the distro version, and 404 | * ```` is the distro codename. 405 | 406 | The following information items can be found in a distro release file 407 | (shown with their keys and data types): 408 | 409 | * ``id`` (string): Distro ID, taken from the first part of the file name 410 | before the hyphen (``-``) or underscore (``_``). 411 | 412 | Note that the distro ID is not normalized or translated to lower case at this 413 | point; this happens only for the result of the :func:`distro.id` function. 414 | 415 | * ``name`` (string): Distro name, as found in the first line of the file. 416 | 417 | * ``version_id`` (string): Distro version, as found in the first line of the 418 | file. If not found, this information item will not exist. 419 | 420 | * ``codename`` (string): Distro codename, as found in the first line of the 421 | file. If not found, this information item will not exist. 422 | 423 | Note that the string in the codename field is not always really a 424 | codename. For example, openSUSE returns "x86_64". 425 | 426 | **Examples:** 427 | 428 | 1. The following distro release file ``/etc/centos-release``: 429 | 430 | .. sourcecode:: text 431 | 432 | CentOS Linux release 7.1.1503 (Core) 433 | 434 | results in these information items: 435 | 436 | =============================== ========================================== 437 | Key Value 438 | =============================== ========================================== 439 | id "centos" 440 | name "CentOS Linux" 441 | version_id "7.1.1503" 442 | codename "Core" 443 | =============================== ========================================== 444 | 445 | 2. The following distro release file ``/etc/oracle-release``: 446 | 447 | .. sourcecode:: text 448 | 449 | Oracle Linux Server release 7.1 450 | 451 | results in these information items: 452 | 453 | =============================== ========================================== 454 | Key Value 455 | =============================== ========================================== 456 | id "oracle" 457 | name "Oracle Linux Server" 458 | version_id "7.1" 459 | =============================== ========================================== 460 | 461 | 3. The following distro release file ``/etc/SuSE-release``: 462 | 463 | .. sourcecode:: text 464 | 465 | openSUSE 42.1 (x86_64) 466 | 467 | results in these information items: 468 | 469 | =============================== ========================================== 470 | Key Value 471 | =============================== ========================================== 472 | id "SuSE" 473 | name "openSUSE" 474 | version_id "42.1" 475 | codename "x86_64" 476 | =============================== ========================================== 477 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /query_local_distro.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2015,2016 Nir Cohen 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from pprint import pformat 17 | 18 | import distro 19 | 20 | 21 | def pprint(obj: object) -> None: 22 | for line in pformat(obj).split("\n"): 23 | print(4 * " " + line) 24 | 25 | 26 | print("os_release_info:") 27 | pprint(distro.os_release_info()) 28 | print("lsb_release_info:") 29 | pprint(distro.lsb_release_info()) 30 | print("distro_release_info:") 31 | pprint(distro.distro_release_info()) 32 | print(f"id: {distro.id()}") 33 | print(f"name: {distro.name()}") 34 | print(f"name_pretty: {distro.name(True)}") 35 | print(f"version: {distro.version()}") 36 | print(f"version_pretty: {distro.version(True)}") 37 | print(f"like: {distro.like()}") 38 | print(f"codename: {distro.codename()}") 39 | print(f"linux_distribution_full: {distro.linux_distribution()}") 40 | print(f"linux_distribution: {distro.linux_distribution(False)}") 41 | print(f"major_version: {distro.major_version()}") 42 | print(f"minor_version: {distro.minor_version()}") 43 | print(f"build_number: {distro.build_number()}") 44 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = distro 3 | version = attr: distro.__version__ 4 | url = https://github.com/python-distro/distro 5 | author = Nir Cohen 6 | author_email = nir36g@gmail.com 7 | license = Apache License, Version 2.0 8 | platforms = All 9 | description= Distro - an OS platform information API 10 | long_description = file: README.md 11 | long_description_content_type = text/markdown 12 | classifiers = 13 | Development Status :: 5 - Production/Stable 14 | Intended Audience :: Developers 15 | Intended Audience :: System Administrators 16 | License :: OSI Approved :: Apache Software License 17 | Operating System :: POSIX :: Linux 18 | Operating System :: POSIX :: BSD 19 | Operating System :: POSIX :: BSD :: FreeBSD 20 | Operating System :: POSIX :: BSD :: NetBSD 21 | Operating System :: POSIX :: BSD :: OpenBSD 22 | Programming Language :: Python :: 3 23 | Programming Language :: Python :: 3 :: Only 24 | Programming Language :: Python :: 3.6 25 | Programming Language :: Python :: 3.7 26 | Programming Language :: Python :: 3.8 27 | Programming Language :: Python :: 3.9 28 | Programming Language :: Python :: 3.10 29 | Programming Language :: Python :: 3.11 30 | Programming Language :: Python :: 3.12 31 | Topic :: Software Development :: Libraries :: Python Modules 32 | Topic :: System :: Operating System 33 | 34 | [options] 35 | package_dir = 36 | = src 37 | packages = distro 38 | python_requires = >=3.6 39 | 40 | [options.package_data] 41 | * = py.typed 42 | 43 | [options.entry_points] 44 | console_scripts = 45 | distro = distro.distro:main 46 | 47 | [flake8] 48 | max-line-length = 88 49 | 50 | [isort] 51 | profile = black 52 | -------------------------------------------------------------------------------- /src/distro/__init__.py: -------------------------------------------------------------------------------- 1 | from .distro import ( 2 | NORMALIZED_DISTRO_ID, 3 | NORMALIZED_LSB_ID, 4 | NORMALIZED_OS_ID, 5 | LinuxDistribution, 6 | __version__, 7 | build_number, 8 | codename, 9 | distro_release_attr, 10 | distro_release_info, 11 | id, 12 | info, 13 | like, 14 | linux_distribution, 15 | lsb_release_attr, 16 | lsb_release_info, 17 | major_version, 18 | minor_version, 19 | name, 20 | os_release_attr, 21 | os_release_info, 22 | uname_attr, 23 | uname_info, 24 | version, 25 | version_parts, 26 | ) 27 | 28 | __all__ = [ 29 | "NORMALIZED_DISTRO_ID", 30 | "NORMALIZED_LSB_ID", 31 | "NORMALIZED_OS_ID", 32 | "LinuxDistribution", 33 | "build_number", 34 | "codename", 35 | "distro_release_attr", 36 | "distro_release_info", 37 | "id", 38 | "info", 39 | "like", 40 | "linux_distribution", 41 | "lsb_release_attr", 42 | "lsb_release_info", 43 | "major_version", 44 | "minor_version", 45 | "name", 46 | "os_release_attr", 47 | "os_release_info", 48 | "uname_attr", 49 | "uname_info", 50 | "version", 51 | "version_parts", 52 | ] 53 | 54 | __version__ = __version__ 55 | -------------------------------------------------------------------------------- /src/distro/__main__.py: -------------------------------------------------------------------------------- 1 | from .distro import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /src/distro/distro.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2015-2021 Nir Cohen 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """ 17 | The ``distro`` package (``distro`` stands for Linux Distribution) provides 18 | information about the Linux distribution it runs on, such as a reliable 19 | machine-readable distro ID, or version information. 20 | 21 | It is the recommended replacement for Python's original 22 | :py:func:`platform.linux_distribution` function, but it provides much more 23 | functionality. An alternative implementation became necessary because Python 24 | 3.5 deprecated this function, and Python 3.8 removed it altogether. Its 25 | predecessor function :py:func:`platform.dist` was already deprecated since 26 | Python 2.6 and removed in Python 3.8. Still, there are many cases in which 27 | access to OS distribution information is needed. See `Python issue 1322 28 | `_ for more information. 29 | """ 30 | 31 | import argparse 32 | import json 33 | import logging 34 | import os 35 | import re 36 | import shlex 37 | import subprocess 38 | import sys 39 | import warnings 40 | from typing import ( 41 | Any, 42 | Callable, 43 | Dict, 44 | Iterable, 45 | Optional, 46 | Sequence, 47 | TextIO, 48 | Tuple, 49 | Type, 50 | ) 51 | 52 | try: 53 | from typing import TypedDict 54 | except ImportError: 55 | # Python 3.7 56 | TypedDict = dict 57 | 58 | __version__ = "1.9.0" 59 | 60 | 61 | class VersionDict(TypedDict): 62 | major: str 63 | minor: str 64 | build_number: str 65 | 66 | 67 | class InfoDict(TypedDict): 68 | id: str 69 | version: str 70 | version_parts: VersionDict 71 | like: str 72 | codename: str 73 | 74 | 75 | _UNIXCONFDIR = os.environ.get("UNIXCONFDIR", "/etc") 76 | _UNIXUSRLIBDIR = os.environ.get("UNIXUSRLIBDIR", "/usr/lib") 77 | _OS_RELEASE_BASENAME = "os-release" 78 | 79 | #: Translation table for normalizing the "ID" attribute defined in os-release 80 | #: files, for use by the :func:`distro.id` method. 81 | #: 82 | #: * Key: Value as defined in the os-release file, translated to lower case, 83 | #: with blanks translated to underscores. 84 | #: 85 | #: * Value: Normalized value. 86 | NORMALIZED_OS_ID = { 87 | "ol": "oracle", # Oracle Linux 88 | "opensuse-leap": "opensuse", # Newer versions of OpenSuSE report as opensuse-leap 89 | } 90 | 91 | #: Translation table for normalizing the "Distributor ID" attribute returned by 92 | #: the lsb_release command, for use by the :func:`distro.id` method. 93 | #: 94 | #: * Key: Value as returned by the lsb_release command, translated to lower 95 | #: case, with blanks translated to underscores. 96 | #: 97 | #: * Value: Normalized value. 98 | NORMALIZED_LSB_ID = { 99 | "enterpriseenterpriseas": "oracle", # Oracle Enterprise Linux 4 100 | "enterpriseenterpriseserver": "oracle", # Oracle Linux 5 101 | "redhatenterpriseworkstation": "rhel", # RHEL 6, 7 Workstation 102 | "redhatenterpriseserver": "rhel", # RHEL 6, 7 Server 103 | "redhatenterprisecomputenode": "rhel", # RHEL 6 ComputeNode 104 | } 105 | 106 | #: Translation table for normalizing the distro ID derived from the file name 107 | #: of distro release files, for use by the :func:`distro.id` method. 108 | #: 109 | #: * Key: Value as derived from the file name of a distro release file, 110 | #: translated to lower case, with blanks translated to underscores. 111 | #: 112 | #: * Value: Normalized value. 113 | NORMALIZED_DISTRO_ID = { 114 | "redhat": "rhel", # RHEL 6.x, 7.x 115 | } 116 | 117 | # Pattern for content of distro release file (reversed) 118 | _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile( 119 | r"(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)" 120 | ) 121 | 122 | # Pattern for base file name of distro release file 123 | _DISTRO_RELEASE_BASENAME_PATTERN = re.compile(r"(\w+)[-_](release|version)$") 124 | 125 | # Base file names to be looked up for if _UNIXCONFDIR is not readable. 126 | _DISTRO_RELEASE_BASENAMES = [ 127 | "SuSE-release", 128 | "altlinux-release", 129 | "arch-release", 130 | "armbian-release", 131 | "base-release", 132 | "centos-release", 133 | "fedora-release", 134 | "gentoo-release", 135 | "mageia-release", 136 | "mandrake-release", 137 | "mandriva-release", 138 | "mandrivalinux-release", 139 | "manjaro-release", 140 | "oracle-release", 141 | "redhat-release", 142 | "rocky-release", 143 | "sl-release", 144 | "slackware-version", 145 | ] 146 | 147 | # Base file names to be ignored when searching for distro release file 148 | _DISTRO_RELEASE_IGNORE_BASENAMES = ( 149 | "debian_version", 150 | "lsb-release", 151 | "oem-release", 152 | _OS_RELEASE_BASENAME, 153 | "system-release", 154 | "plesk-release", 155 | "iredmail-release", 156 | "board-release", 157 | "ec2_version", 158 | ) 159 | 160 | 161 | def linux_distribution(full_distribution_name: bool = True) -> Tuple[str, str, str]: 162 | """ 163 | .. deprecated:: 1.6.0 164 | 165 | :func:`distro.linux_distribution()` is deprecated. It should only be 166 | used as a compatibility shim with Python's 167 | :py:func:`platform.linux_distribution()`. Please use :func:`distro.id`, 168 | :func:`distro.version` and :func:`distro.name` instead. 169 | 170 | Return information about the current OS distribution as a tuple 171 | ``(id_name, version, codename)`` with items as follows: 172 | 173 | * ``id_name``: If *full_distribution_name* is false, the result of 174 | :func:`distro.id`. Otherwise, the result of :func:`distro.name`. 175 | 176 | * ``version``: The result of :func:`distro.version`. 177 | 178 | * ``codename``: The extra item (usually in parentheses) after the 179 | os-release version number, or the result of :func:`distro.codename`. 180 | 181 | The interface of this function is compatible with the original 182 | :py:func:`platform.linux_distribution` function, supporting a subset of 183 | its parameters. 184 | 185 | The data it returns may not exactly be the same, because it uses more data 186 | sources than the original function, and that may lead to different data if 187 | the OS distribution is not consistent across multiple data sources it 188 | provides (there are indeed such distributions ...). 189 | 190 | Another reason for differences is the fact that the :func:`distro.id` 191 | method normalizes the distro ID string to a reliable machine-readable value 192 | for a number of popular OS distributions. 193 | """ 194 | warnings.warn( 195 | "distro.linux_distribution() is deprecated. It should only be used as a " 196 | "compatibility shim with Python's platform.linux_distribution(). Please use " 197 | "distro.id(), distro.version() and distro.name() instead.", 198 | DeprecationWarning, 199 | stacklevel=2, 200 | ) 201 | return _distro.linux_distribution(full_distribution_name) 202 | 203 | 204 | def id() -> str: 205 | """ 206 | Return the distro ID of the current distribution, as a 207 | machine-readable string. 208 | 209 | For a number of OS distributions, the returned distro ID value is 210 | *reliable*, in the sense that it is documented and that it does not change 211 | across releases of the distribution. 212 | 213 | This package maintains the following reliable distro ID values: 214 | 215 | ============== ========================================= 216 | Distro ID Distribution 217 | ============== ========================================= 218 | "ubuntu" Ubuntu 219 | "debian" Debian 220 | "rhel" RedHat Enterprise Linux 221 | "centos" CentOS 222 | "fedora" Fedora 223 | "sles" SUSE Linux Enterprise Server 224 | "opensuse" openSUSE 225 | "amzn" Amazon Linux 226 | "arch" Arch Linux 227 | "buildroot" Buildroot 228 | "cloudlinux" CloudLinux OS 229 | "exherbo" Exherbo Linux 230 | "gentoo" GenToo Linux 231 | "ibm_powerkvm" IBM PowerKVM 232 | "kvmibm" KVM for IBM z Systems 233 | "linuxmint" Linux Mint 234 | "mageia" Mageia 235 | "mandriva" Mandriva Linux 236 | "parallels" Parallels 237 | "pidora" Pidora 238 | "raspbian" Raspbian 239 | "oracle" Oracle Linux (and Oracle Enterprise Linux) 240 | "scientific" Scientific Linux 241 | "slackware" Slackware 242 | "xenserver" XenServer 243 | "openbsd" OpenBSD 244 | "netbsd" NetBSD 245 | "freebsd" FreeBSD 246 | "midnightbsd" MidnightBSD 247 | "rocky" Rocky Linux 248 | "aix" AIX 249 | "guix" Guix System 250 | "altlinux" ALT Linux 251 | ============== ========================================= 252 | 253 | If you have a need to get distros for reliable IDs added into this set, 254 | or if you find that the :func:`distro.id` function returns a different 255 | distro ID for one of the listed distros, please create an issue in the 256 | `distro issue tracker`_. 257 | 258 | **Lookup hierarchy and transformations:** 259 | 260 | First, the ID is obtained from the following sources, in the specified 261 | order. The first available and non-empty value is used: 262 | 263 | * the value of the "ID" attribute of the os-release file, 264 | 265 | * the value of the "Distributor ID" attribute returned by the lsb_release 266 | command, 267 | 268 | * the first part of the file name of the distro release file, 269 | 270 | The so determined ID value then passes the following transformations, 271 | before it is returned by this method: 272 | 273 | * it is translated to lower case, 274 | 275 | * blanks (which should not be there anyway) are translated to underscores, 276 | 277 | * a normalization of the ID is performed, based upon 278 | `normalization tables`_. The purpose of this normalization is to ensure 279 | that the ID is as reliable as possible, even across incompatible changes 280 | in the OS distributions. A common reason for an incompatible change is 281 | the addition of an os-release file, or the addition of the lsb_release 282 | command, with ID values that differ from what was previously determined 283 | from the distro release file name. 284 | """ 285 | return _distro.id() 286 | 287 | 288 | def name(pretty: bool = False) -> str: 289 | """ 290 | Return the name of the current OS distribution, as a human-readable 291 | string. 292 | 293 | If *pretty* is false, the name is returned without version or codename. 294 | (e.g. "CentOS Linux") 295 | 296 | If *pretty* is true, the version and codename are appended. 297 | (e.g. "CentOS Linux 7.1.1503 (Core)") 298 | 299 | **Lookup hierarchy:** 300 | 301 | The name is obtained from the following sources, in the specified order. 302 | The first available and non-empty value is used: 303 | 304 | * If *pretty* is false: 305 | 306 | - the value of the "NAME" attribute of the os-release file, 307 | 308 | - the value of the "Distributor ID" attribute returned by the lsb_release 309 | command, 310 | 311 | - the value of the "" field of the distro release file. 312 | 313 | * If *pretty* is true: 314 | 315 | - the value of the "PRETTY_NAME" attribute of the os-release file, 316 | 317 | - the value of the "Description" attribute returned by the lsb_release 318 | command, 319 | 320 | - the value of the "" field of the distro release file, appended 321 | with the value of the pretty version ("" and "" 322 | fields) of the distro release file, if available. 323 | """ 324 | return _distro.name(pretty) 325 | 326 | 327 | def version(pretty: bool = False, best: bool = False) -> str: 328 | """ 329 | Return the version of the current OS distribution, as a human-readable 330 | string. 331 | 332 | If *pretty* is false, the version is returned without codename (e.g. 333 | "7.0"). 334 | 335 | If *pretty* is true, the codename in parenthesis is appended, if the 336 | codename is non-empty (e.g. "7.0 (Maipo)"). 337 | 338 | Some distributions provide version numbers with different precisions in 339 | the different sources of distribution information. Examining the different 340 | sources in a fixed priority order does not always yield the most precise 341 | version (e.g. for Debian 8.2, or CentOS 7.1). 342 | 343 | Some other distributions may not provide this kind of information. In these 344 | cases, an empty string would be returned. This behavior can be observed 345 | with rolling releases distributions (e.g. Arch Linux). 346 | 347 | The *best* parameter can be used to control the approach for the returned 348 | version: 349 | 350 | If *best* is false, the first non-empty version number in priority order of 351 | the examined sources is returned. 352 | 353 | If *best* is true, the most precise version number out of all examined 354 | sources is returned. 355 | 356 | **Lookup hierarchy:** 357 | 358 | In all cases, the version number is obtained from the following sources. 359 | If *best* is false, this order represents the priority order: 360 | 361 | * the value of the "VERSION_ID" attribute of the os-release file, 362 | * the value of the "Release" attribute returned by the lsb_release 363 | command, 364 | * the version number parsed from the "" field of the first line 365 | of the distro release file, 366 | * the version number parsed from the "PRETTY_NAME" attribute of the 367 | os-release file, if it follows the format of the distro release files. 368 | * the version number parsed from the "Description" attribute returned by 369 | the lsb_release command, if it follows the format of the distro release 370 | files. 371 | """ 372 | return _distro.version(pretty, best) 373 | 374 | 375 | def version_parts(best: bool = False) -> Tuple[str, str, str]: 376 | """ 377 | Return the version of the current OS distribution as a tuple 378 | ``(major, minor, build_number)`` with items as follows: 379 | 380 | * ``major``: The result of :func:`distro.major_version`. 381 | 382 | * ``minor``: The result of :func:`distro.minor_version`. 383 | 384 | * ``build_number``: The result of :func:`distro.build_number`. 385 | 386 | For a description of the *best* parameter, see the :func:`distro.version` 387 | method. 388 | """ 389 | return _distro.version_parts(best) 390 | 391 | 392 | def major_version(best: bool = False) -> str: 393 | """ 394 | Return the major version of the current OS distribution, as a string, 395 | if provided. 396 | Otherwise, the empty string is returned. The major version is the first 397 | part of the dot-separated version string. 398 | 399 | For a description of the *best* parameter, see the :func:`distro.version` 400 | method. 401 | """ 402 | return _distro.major_version(best) 403 | 404 | 405 | def minor_version(best: bool = False) -> str: 406 | """ 407 | Return the minor version of the current OS distribution, as a string, 408 | if provided. 409 | Otherwise, the empty string is returned. The minor version is the second 410 | part of the dot-separated version string. 411 | 412 | For a description of the *best* parameter, see the :func:`distro.version` 413 | method. 414 | """ 415 | return _distro.minor_version(best) 416 | 417 | 418 | def build_number(best: bool = False) -> str: 419 | """ 420 | Return the build number of the current OS distribution, as a string, 421 | if provided. 422 | Otherwise, the empty string is returned. The build number is the third part 423 | of the dot-separated version string. 424 | 425 | For a description of the *best* parameter, see the :func:`distro.version` 426 | method. 427 | """ 428 | return _distro.build_number(best) 429 | 430 | 431 | def like() -> str: 432 | """ 433 | Return a space-separated list of distro IDs of distributions that are 434 | closely related to the current OS distribution in regards to packaging 435 | and programming interfaces, for example distributions the current 436 | distribution is a derivative from. 437 | 438 | **Lookup hierarchy:** 439 | 440 | This information item is only provided by the os-release file. 441 | For details, see the description of the "ID_LIKE" attribute in the 442 | `os-release man page 443 | `_. 444 | """ 445 | return _distro.like() 446 | 447 | 448 | def codename() -> str: 449 | """ 450 | Return the codename for the release of the current OS distribution, 451 | as a string. 452 | 453 | If the distribution does not have a codename, an empty string is returned. 454 | 455 | Note that the returned codename is not always really a codename. For 456 | example, openSUSE returns "x86_64". This function does not handle such 457 | cases in any special way and just returns the string it finds, if any. 458 | 459 | **Lookup hierarchy:** 460 | 461 | * the codename within the "VERSION" attribute of the os-release file, if 462 | provided, 463 | 464 | * the value of the "Codename" attribute returned by the lsb_release 465 | command, 466 | 467 | * the value of the "" field of the distro release file. 468 | """ 469 | return _distro.codename() 470 | 471 | 472 | def info(pretty: bool = False, best: bool = False) -> InfoDict: 473 | """ 474 | Return certain machine-readable information items about the current OS 475 | distribution in a dictionary, as shown in the following example: 476 | 477 | .. sourcecode:: python 478 | 479 | { 480 | 'id': 'rhel', 481 | 'version': '7.0', 482 | 'version_parts': { 483 | 'major': '7', 484 | 'minor': '0', 485 | 'build_number': '' 486 | }, 487 | 'like': 'fedora', 488 | 'codename': 'Maipo' 489 | } 490 | 491 | The dictionary structure and keys are always the same, regardless of which 492 | information items are available in the underlying data sources. The values 493 | for the various keys are as follows: 494 | 495 | * ``id``: The result of :func:`distro.id`. 496 | 497 | * ``version``: The result of :func:`distro.version`. 498 | 499 | * ``version_parts -> major``: The result of :func:`distro.major_version`. 500 | 501 | * ``version_parts -> minor``: The result of :func:`distro.minor_version`. 502 | 503 | * ``version_parts -> build_number``: The result of 504 | :func:`distro.build_number`. 505 | 506 | * ``like``: The result of :func:`distro.like`. 507 | 508 | * ``codename``: The result of :func:`distro.codename`. 509 | 510 | For a description of the *pretty* and *best* parameters, see the 511 | :func:`distro.version` method. 512 | """ 513 | return _distro.info(pretty, best) 514 | 515 | 516 | def os_release_info() -> Dict[str, str]: 517 | """ 518 | Return a dictionary containing key-value pairs for the information items 519 | from the os-release file data source of the current OS distribution. 520 | 521 | See `os-release file`_ for details about these information items. 522 | """ 523 | return _distro.os_release_info() 524 | 525 | 526 | def lsb_release_info() -> Dict[str, str]: 527 | """ 528 | Return a dictionary containing key-value pairs for the information items 529 | from the lsb_release command data source of the current OS distribution. 530 | 531 | See `lsb_release command output`_ for details about these information 532 | items. 533 | """ 534 | return _distro.lsb_release_info() 535 | 536 | 537 | def distro_release_info() -> Dict[str, str]: 538 | """ 539 | Return a dictionary containing key-value pairs for the information items 540 | from the distro release file data source of the current OS distribution. 541 | 542 | See `distro release file`_ for details about these information items. 543 | """ 544 | return _distro.distro_release_info() 545 | 546 | 547 | def uname_info() -> Dict[str, str]: 548 | """ 549 | Return a dictionary containing key-value pairs for the information items 550 | from the distro release file data source of the current OS distribution. 551 | """ 552 | return _distro.uname_info() 553 | 554 | 555 | def os_release_attr(attribute: str) -> str: 556 | """ 557 | Return a single named information item from the os-release file data source 558 | of the current OS distribution. 559 | 560 | Parameters: 561 | 562 | * ``attribute`` (string): Key of the information item. 563 | 564 | Returns: 565 | 566 | * (string): Value of the information item, if the item exists. 567 | The empty string, if the item does not exist. 568 | 569 | See `os-release file`_ for details about these information items. 570 | """ 571 | return _distro.os_release_attr(attribute) 572 | 573 | 574 | def lsb_release_attr(attribute: str) -> str: 575 | """ 576 | Return a single named information item from the lsb_release command output 577 | data source of the current OS distribution. 578 | 579 | Parameters: 580 | 581 | * ``attribute`` (string): Key of the information item. 582 | 583 | Returns: 584 | 585 | * (string): Value of the information item, if the item exists. 586 | The empty string, if the item does not exist. 587 | 588 | See `lsb_release command output`_ for details about these information 589 | items. 590 | """ 591 | return _distro.lsb_release_attr(attribute) 592 | 593 | 594 | def distro_release_attr(attribute: str) -> str: 595 | """ 596 | Return a single named information item from the distro release file 597 | data source of the current OS distribution. 598 | 599 | Parameters: 600 | 601 | * ``attribute`` (string): Key of the information item. 602 | 603 | Returns: 604 | 605 | * (string): Value of the information item, if the item exists. 606 | The empty string, if the item does not exist. 607 | 608 | See `distro release file`_ for details about these information items. 609 | """ 610 | return _distro.distro_release_attr(attribute) 611 | 612 | 613 | def uname_attr(attribute: str) -> str: 614 | """ 615 | Return a single named information item from the distro release file 616 | data source of the current OS distribution. 617 | 618 | Parameters: 619 | 620 | * ``attribute`` (string): Key of the information item. 621 | 622 | Returns: 623 | 624 | * (string): Value of the information item, if the item exists. 625 | The empty string, if the item does not exist. 626 | """ 627 | return _distro.uname_attr(attribute) 628 | 629 | 630 | try: 631 | from functools import cached_property 632 | except ImportError: 633 | # Python < 3.8 634 | class cached_property: # type: ignore 635 | """A version of @property which caches the value. On access, it calls the 636 | underlying function and sets the value in `__dict__` so future accesses 637 | will not re-call the property. 638 | """ 639 | 640 | def __init__(self, f: Callable[[Any], Any]) -> None: 641 | self._fname = f.__name__ 642 | self._f = f 643 | 644 | def __get__(self, obj: Any, owner: Type[Any]) -> Any: 645 | assert obj is not None, f"call {self._fname} on an instance" 646 | ret = obj.__dict__[self._fname] = self._f(obj) 647 | return ret 648 | 649 | 650 | class LinuxDistribution: 651 | """ 652 | Provides information about a OS distribution. 653 | 654 | This package creates a private module-global instance of this class with 655 | default initialization arguments, that is used by the 656 | `consolidated accessor functions`_ and `single source accessor functions`_. 657 | By using default initialization arguments, that module-global instance 658 | returns data about the current OS distribution (i.e. the distro this 659 | package runs on). 660 | 661 | Normally, it is not necessary to create additional instances of this class. 662 | However, in situations where control is needed over the exact data sources 663 | that are used, instances of this class can be created with a specific 664 | distro release file, or a specific os-release file, or without invoking the 665 | lsb_release command. 666 | """ 667 | 668 | def __init__( 669 | self, 670 | include_lsb: Optional[bool] = None, 671 | os_release_file: str = "", 672 | distro_release_file: str = "", 673 | include_uname: Optional[bool] = None, 674 | root_dir: Optional[str] = None, 675 | include_oslevel: Optional[bool] = None, 676 | ) -> None: 677 | """ 678 | The initialization method of this class gathers information from the 679 | available data sources, and stores that in private instance attributes. 680 | Subsequent access to the information items uses these private instance 681 | attributes, so that the data sources are read only once. 682 | 683 | Parameters: 684 | 685 | * ``include_lsb`` (bool): Controls whether the 686 | `lsb_release command output`_ is included as a data source. 687 | 688 | If the lsb_release command is not available in the program execution 689 | path, the data source for the lsb_release command will be empty. 690 | 691 | * ``os_release_file`` (string): The path name of the 692 | `os-release file`_ that is to be used as a data source. 693 | 694 | An empty string (the default) will cause the default path name to 695 | be used (see `os-release file`_ for details). 696 | 697 | If the specified or defaulted os-release file does not exist, the 698 | data source for the os-release file will be empty. 699 | 700 | * ``distro_release_file`` (string): The path name of the 701 | `distro release file`_ that is to be used as a data source. 702 | 703 | An empty string (the default) will cause a default search algorithm 704 | to be used (see `distro release file`_ for details). 705 | 706 | If the specified distro release file does not exist, or if no default 707 | distro release file can be found, the data source for the distro 708 | release file will be empty. 709 | 710 | * ``include_uname`` (bool): Controls whether uname command output is 711 | included as a data source. If the uname command is not available in 712 | the program execution path the data source for the uname command will 713 | be empty. 714 | 715 | * ``root_dir`` (string): The absolute path to the root directory to use 716 | to find distro-related information files. Note that ``include_*`` 717 | parameters must not be enabled in combination with ``root_dir``. 718 | 719 | * ``include_oslevel`` (bool): Controls whether (AIX) oslevel command 720 | output is included as a data source. If the oslevel command is not 721 | available in the program execution path the data source will be 722 | empty. 723 | 724 | Public instance attributes: 725 | 726 | * ``os_release_file`` (string): The path name of the 727 | `os-release file`_ that is actually used as a data source. The 728 | empty string if no distro release file is used as a data source. 729 | 730 | * ``distro_release_file`` (string): The path name of the 731 | `distro release file`_ that is actually used as a data source. The 732 | empty string if no distro release file is used as a data source. 733 | 734 | * ``include_lsb`` (bool): The result of the ``include_lsb`` parameter. 735 | This controls whether the lsb information will be loaded. 736 | 737 | * ``include_uname`` (bool): The result of the ``include_uname`` 738 | parameter. This controls whether the uname information will 739 | be loaded. 740 | 741 | * ``include_oslevel`` (bool): The result of the ``include_oslevel`` 742 | parameter. This controls whether (AIX) oslevel information will be 743 | loaded. 744 | 745 | * ``root_dir`` (string): The result of the ``root_dir`` parameter. 746 | The absolute path to the root directory to use to find distro-related 747 | information files. 748 | 749 | Raises: 750 | 751 | * :py:exc:`ValueError`: Initialization parameters combination is not 752 | supported. 753 | 754 | * :py:exc:`OSError`: Some I/O issue with an os-release file or distro 755 | release file. 756 | 757 | * :py:exc:`UnicodeError`: A data source has unexpected characters or 758 | uses an unexpected encoding. 759 | """ 760 | self.root_dir = root_dir 761 | self.etc_dir = os.path.join(root_dir, "etc") if root_dir else _UNIXCONFDIR 762 | self.usr_lib_dir = ( 763 | os.path.join(root_dir, "usr/lib") if root_dir else _UNIXUSRLIBDIR 764 | ) 765 | 766 | if os_release_file: 767 | self.os_release_file = os_release_file 768 | else: 769 | etc_dir_os_release_file = os.path.join(self.etc_dir, _OS_RELEASE_BASENAME) 770 | usr_lib_os_release_file = os.path.join( 771 | self.usr_lib_dir, _OS_RELEASE_BASENAME 772 | ) 773 | 774 | # NOTE: The idea is to respect order **and** have it set 775 | # at all times for API backwards compatibility. 776 | if os.path.isfile(etc_dir_os_release_file) or not os.path.isfile( 777 | usr_lib_os_release_file 778 | ): 779 | self.os_release_file = etc_dir_os_release_file 780 | else: 781 | self.os_release_file = usr_lib_os_release_file 782 | 783 | self.distro_release_file = distro_release_file or "" # updated later 784 | 785 | is_root_dir_defined = root_dir is not None 786 | if is_root_dir_defined and (include_lsb or include_uname or include_oslevel): 787 | raise ValueError( 788 | "Including subprocess data sources from specific root_dir is disallowed" 789 | " to prevent false information" 790 | ) 791 | self.include_lsb = ( 792 | include_lsb if include_lsb is not None else not is_root_dir_defined 793 | ) 794 | self.include_uname = ( 795 | include_uname if include_uname is not None else not is_root_dir_defined 796 | ) 797 | self.include_oslevel = ( 798 | include_oslevel if include_oslevel is not None else not is_root_dir_defined 799 | ) 800 | 801 | def __repr__(self) -> str: 802 | """Return repr of all info""" 803 | return ( 804 | "LinuxDistribution(" 805 | "os_release_file={self.os_release_file!r}, " 806 | "distro_release_file={self.distro_release_file!r}, " 807 | "include_lsb={self.include_lsb!r}, " 808 | "include_uname={self.include_uname!r}, " 809 | "include_oslevel={self.include_oslevel!r}, " 810 | "root_dir={self.root_dir!r}, " 811 | "_os_release_info={self._os_release_info!r}, " 812 | "_lsb_release_info={self._lsb_release_info!r}, " 813 | "_distro_release_info={self._distro_release_info!r}, " 814 | "_uname_info={self._uname_info!r}, " 815 | "_oslevel_info={self._oslevel_info!r})".format(self=self) 816 | ) 817 | 818 | def linux_distribution( 819 | self, full_distribution_name: bool = True 820 | ) -> Tuple[str, str, str]: 821 | """ 822 | Return information about the OS distribution that is compatible 823 | with Python's :func:`platform.linux_distribution`, supporting a subset 824 | of its parameters. 825 | 826 | For details, see :func:`distro.linux_distribution`. 827 | """ 828 | return ( 829 | self.name() if full_distribution_name else self.id(), 830 | self.version(), 831 | self._os_release_info.get("release_codename") or self.codename(), 832 | ) 833 | 834 | def id(self) -> str: 835 | """Return the distro ID of the OS distribution, as a string. 836 | 837 | For details, see :func:`distro.id`. 838 | """ 839 | 840 | def normalize(distro_id: str, table: Dict[str, str]) -> str: 841 | distro_id = distro_id.lower().replace(" ", "_") 842 | return table.get(distro_id, distro_id) 843 | 844 | distro_id = self.os_release_attr("id") 845 | if distro_id: 846 | return normalize(distro_id, NORMALIZED_OS_ID) 847 | 848 | distro_id = self.lsb_release_attr("distributor_id") 849 | if distro_id: 850 | return normalize(distro_id, NORMALIZED_LSB_ID) 851 | 852 | distro_id = self.distro_release_attr("id") 853 | if distro_id: 854 | return normalize(distro_id, NORMALIZED_DISTRO_ID) 855 | 856 | distro_id = self.uname_attr("id") 857 | if distro_id: 858 | return normalize(distro_id, NORMALIZED_DISTRO_ID) 859 | 860 | return "" 861 | 862 | def name(self, pretty: bool = False) -> str: 863 | """ 864 | Return the name of the OS distribution, as a string. 865 | 866 | For details, see :func:`distro.name`. 867 | """ 868 | name = ( 869 | self.os_release_attr("name") 870 | or self.lsb_release_attr("distributor_id") 871 | or self.distro_release_attr("name") 872 | or self.uname_attr("name") 873 | ) 874 | if pretty: 875 | name = self.os_release_attr("pretty_name") or self.lsb_release_attr( 876 | "description" 877 | ) 878 | if not name: 879 | name = self.distro_release_attr("name") or self.uname_attr("name") 880 | version = self.version(pretty=True) 881 | if version: 882 | name = f"{name} {version}" 883 | return name or "" 884 | 885 | def version(self, pretty: bool = False, best: bool = False) -> str: 886 | """ 887 | Return the version of the OS distribution, as a string. 888 | 889 | For details, see :func:`distro.version`. 890 | """ 891 | versions = [ 892 | self.os_release_attr("version_id"), 893 | self.lsb_release_attr("release"), 894 | self.distro_release_attr("version_id"), 895 | self._parse_distro_release_content(self.os_release_attr("pretty_name")).get( 896 | "version_id", "" 897 | ), 898 | self._parse_distro_release_content( 899 | self.lsb_release_attr("description") 900 | ).get("version_id", ""), 901 | self.uname_attr("release"), 902 | ] 903 | if self.uname_attr("id").startswith("aix"): 904 | # On AIX platforms, prefer oslevel command output. 905 | versions.insert(0, self.oslevel_info()) 906 | elif self.id() == "debian" or "debian" in self.like().split(): 907 | # On Debian-like, add debian_version file content to candidates list. 908 | versions.append(self._debian_version) 909 | if self._distro_release_info.get("id") == "armbian": 910 | # On Armbian, add version from armbian-release file to candidates list. 911 | versions.append(self._armbian_version) 912 | version = "" 913 | if best: 914 | # This algorithm uses the last version in priority order that has 915 | # the best precision. If the versions are not in conflict, that 916 | # does not matter; otherwise, using the last one instead of the 917 | # first one might be considered a surprise. 918 | for v in versions: 919 | if v.count(".") > version.count(".") or version == "": 920 | version = v 921 | else: 922 | for v in versions: 923 | if v != "": 924 | version = v 925 | break 926 | if pretty and version and self.codename(): 927 | version = f"{version} ({self.codename()})" 928 | return version 929 | 930 | def version_parts(self, best: bool = False) -> Tuple[str, str, str]: 931 | """ 932 | Return the version of the OS distribution, as a tuple of version 933 | numbers. 934 | 935 | For details, see :func:`distro.version_parts`. 936 | """ 937 | version_str = self.version(best=best) 938 | if version_str: 939 | version_regex = re.compile(r"(\d+)\.?(\d+)?\.?(\d+)?") 940 | matches = version_regex.match(version_str) 941 | if matches: 942 | major, minor, build_number = matches.groups() 943 | return major, minor or "", build_number or "" 944 | return "", "", "" 945 | 946 | def major_version(self, best: bool = False) -> str: 947 | """ 948 | Return the major version number of the current distribution. 949 | 950 | For details, see :func:`distro.major_version`. 951 | """ 952 | return self.version_parts(best)[0] 953 | 954 | def minor_version(self, best: bool = False) -> str: 955 | """ 956 | Return the minor version number of the current distribution. 957 | 958 | For details, see :func:`distro.minor_version`. 959 | """ 960 | return self.version_parts(best)[1] 961 | 962 | def build_number(self, best: bool = False) -> str: 963 | """ 964 | Return the build number of the current distribution. 965 | 966 | For details, see :func:`distro.build_number`. 967 | """ 968 | return self.version_parts(best)[2] 969 | 970 | def like(self) -> str: 971 | """ 972 | Return the IDs of distributions that are like the OS distribution. 973 | 974 | For details, see :func:`distro.like`. 975 | """ 976 | return self.os_release_attr("id_like") or "" 977 | 978 | def codename(self) -> str: 979 | """ 980 | Return the codename of the OS distribution. 981 | 982 | For details, see :func:`distro.codename`. 983 | """ 984 | try: 985 | # Handle os_release specially since distros might purposefully set 986 | # this to empty string to have no codename 987 | return self._os_release_info["codename"] 988 | except KeyError: 989 | return ( 990 | self.lsb_release_attr("codename") 991 | or self.distro_release_attr("codename") 992 | or "" 993 | ) 994 | 995 | def info(self, pretty: bool = False, best: bool = False) -> InfoDict: 996 | """ 997 | Return certain machine-readable information about the OS 998 | distribution. 999 | 1000 | For details, see :func:`distro.info`. 1001 | """ 1002 | return InfoDict( 1003 | id=self.id(), 1004 | version=self.version(pretty, best), 1005 | version_parts=VersionDict( 1006 | major=self.major_version(best), 1007 | minor=self.minor_version(best), 1008 | build_number=self.build_number(best), 1009 | ), 1010 | like=self.like(), 1011 | codename=self.codename(), 1012 | ) 1013 | 1014 | def os_release_info(self) -> Dict[str, str]: 1015 | """ 1016 | Return a dictionary containing key-value pairs for the information 1017 | items from the os-release file data source of the OS distribution. 1018 | 1019 | For details, see :func:`distro.os_release_info`. 1020 | """ 1021 | return self._os_release_info 1022 | 1023 | def lsb_release_info(self) -> Dict[str, str]: 1024 | """ 1025 | Return a dictionary containing key-value pairs for the information 1026 | items from the lsb_release command data source of the OS 1027 | distribution. 1028 | 1029 | For details, see :func:`distro.lsb_release_info`. 1030 | """ 1031 | return self._lsb_release_info 1032 | 1033 | def distro_release_info(self) -> Dict[str, str]: 1034 | """ 1035 | Return a dictionary containing key-value pairs for the information 1036 | items from the distro release file data source of the OS 1037 | distribution. 1038 | 1039 | For details, see :func:`distro.distro_release_info`. 1040 | """ 1041 | return self._distro_release_info 1042 | 1043 | def uname_info(self) -> Dict[str, str]: 1044 | """ 1045 | Return a dictionary containing key-value pairs for the information 1046 | items from the uname command data source of the OS distribution. 1047 | 1048 | For details, see :func:`distro.uname_info`. 1049 | """ 1050 | return self._uname_info 1051 | 1052 | def oslevel_info(self) -> str: 1053 | """ 1054 | Return AIX' oslevel command output. 1055 | """ 1056 | return self._oslevel_info 1057 | 1058 | def os_release_attr(self, attribute: str) -> str: 1059 | """ 1060 | Return a single named information item from the os-release file data 1061 | source of the OS distribution. 1062 | 1063 | For details, see :func:`distro.os_release_attr`. 1064 | """ 1065 | return self._os_release_info.get(attribute, "") 1066 | 1067 | def lsb_release_attr(self, attribute: str) -> str: 1068 | """ 1069 | Return a single named information item from the lsb_release command 1070 | output data source of the OS distribution. 1071 | 1072 | For details, see :func:`distro.lsb_release_attr`. 1073 | """ 1074 | return self._lsb_release_info.get(attribute, "") 1075 | 1076 | def distro_release_attr(self, attribute: str) -> str: 1077 | """ 1078 | Return a single named information item from the distro release file 1079 | data source of the OS distribution. 1080 | 1081 | For details, see :func:`distro.distro_release_attr`. 1082 | """ 1083 | return self._distro_release_info.get(attribute, "") 1084 | 1085 | def uname_attr(self, attribute: str) -> str: 1086 | """ 1087 | Return a single named information item from the uname command 1088 | output data source of the OS distribution. 1089 | 1090 | For details, see :func:`distro.uname_attr`. 1091 | """ 1092 | return self._uname_info.get(attribute, "") 1093 | 1094 | @cached_property 1095 | def _os_release_info(self) -> Dict[str, str]: 1096 | """ 1097 | Get the information items from the specified os-release file. 1098 | 1099 | Returns: 1100 | A dictionary containing all information items. 1101 | """ 1102 | if os.path.isfile(self.os_release_file): 1103 | with open(self.os_release_file, encoding="utf-8") as release_file: 1104 | return self._parse_os_release_content(release_file) 1105 | return {} 1106 | 1107 | @staticmethod 1108 | def _parse_os_release_content(lines: TextIO) -> Dict[str, str]: 1109 | """ 1110 | Parse the lines of an os-release file. 1111 | 1112 | Parameters: 1113 | 1114 | * lines: Iterable through the lines in the os-release file. 1115 | Each line must be a unicode string or a UTF-8 encoded byte 1116 | string. 1117 | 1118 | Returns: 1119 | A dictionary containing all information items. 1120 | """ 1121 | props = {} 1122 | lexer = shlex.shlex(lines, posix=True) 1123 | lexer.whitespace_split = True 1124 | 1125 | tokens = list(lexer) 1126 | for token in tokens: 1127 | # At this point, all shell-like parsing has been done (i.e. 1128 | # comments processed, quotes and backslash escape sequences 1129 | # processed, multi-line values assembled, trailing newlines 1130 | # stripped, etc.), so the tokens are now either: 1131 | # * variable assignments: var=value 1132 | # * commands or their arguments (not allowed in os-release) 1133 | # Ignore any tokens that are not variable assignments 1134 | if "=" in token: 1135 | k, v = token.split("=", 1) 1136 | props[k.lower()] = v 1137 | 1138 | if "version" in props: 1139 | # extract release codename (if any) from version attribute 1140 | match = re.search(r"\((\D+)\)|,\s*(\D+)", props["version"]) 1141 | if match: 1142 | release_codename = match.group(1) or match.group(2) 1143 | props["codename"] = props["release_codename"] = release_codename 1144 | 1145 | if "version_codename" in props: 1146 | # os-release added a version_codename field. Use that in 1147 | # preference to anything else Note that some distros purposefully 1148 | # do not have code names. They should be setting 1149 | # version_codename="" 1150 | props["codename"] = props["version_codename"] 1151 | elif "ubuntu_codename" in props: 1152 | # Same as above but a non-standard field name used on older Ubuntus 1153 | props["codename"] = props["ubuntu_codename"] 1154 | 1155 | return props 1156 | 1157 | @cached_property 1158 | def _lsb_release_info(self) -> Dict[str, str]: 1159 | """ 1160 | Get the information items from the lsb_release command output. 1161 | 1162 | Returns: 1163 | A dictionary containing all information items. 1164 | """ 1165 | if not self.include_lsb: 1166 | return {} 1167 | try: 1168 | cmd = ("lsb_release", "-a") 1169 | stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) 1170 | # Command not found or lsb_release returned error 1171 | except (OSError, subprocess.CalledProcessError): 1172 | return {} 1173 | content = self._to_str(stdout).splitlines() 1174 | return self._parse_lsb_release_content(content) 1175 | 1176 | @staticmethod 1177 | def _parse_lsb_release_content(lines: Iterable[str]) -> Dict[str, str]: 1178 | """ 1179 | Parse the output of the lsb_release command. 1180 | 1181 | Parameters: 1182 | 1183 | * lines: Iterable through the lines of the lsb_release output. 1184 | Each line must be a unicode string or a UTF-8 encoded byte 1185 | string. 1186 | 1187 | Returns: 1188 | A dictionary containing all information items. 1189 | """ 1190 | props = {} 1191 | for line in lines: 1192 | kv = line.strip("\n").split(":", 1) 1193 | if len(kv) != 2: 1194 | # Ignore lines without colon. 1195 | continue 1196 | k, v = kv 1197 | props.update({k.replace(" ", "_").lower(): v.strip()}) 1198 | return props 1199 | 1200 | @cached_property 1201 | def _uname_info(self) -> Dict[str, str]: 1202 | if not self.include_uname: 1203 | return {} 1204 | try: 1205 | cmd = ("uname", "-rs") 1206 | stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) 1207 | except (OSError, subprocess.CalledProcessError): 1208 | return {} 1209 | content = self._to_str(stdout).splitlines() 1210 | return self._parse_uname_content(content) 1211 | 1212 | @cached_property 1213 | def _oslevel_info(self) -> str: 1214 | if not self.include_oslevel: 1215 | return "" 1216 | try: 1217 | stdout = subprocess.check_output("oslevel", stderr=subprocess.DEVNULL) 1218 | except (OSError, subprocess.CalledProcessError): 1219 | return "" 1220 | return self._to_str(stdout).strip() 1221 | 1222 | @cached_property 1223 | def _debian_version(self) -> str: 1224 | try: 1225 | with open( 1226 | os.path.join(self.etc_dir, "debian_version"), encoding="ascii" 1227 | ) as fp: 1228 | return fp.readline().rstrip() 1229 | except FileNotFoundError: 1230 | return "" 1231 | 1232 | @cached_property 1233 | def _armbian_version(self) -> str: 1234 | try: 1235 | with open( 1236 | os.path.join(self.etc_dir, "armbian-release"), encoding="ascii" 1237 | ) as fp: 1238 | return self._parse_os_release_content(fp).get("version", "") 1239 | except FileNotFoundError: 1240 | return "" 1241 | 1242 | @staticmethod 1243 | def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]: 1244 | if not lines: 1245 | return {} 1246 | props = {} 1247 | match = re.search(r"^([^\s]+)\s+([\d\.]+)", lines[0].strip()) 1248 | if match: 1249 | name, version = match.groups() 1250 | 1251 | # This is to prevent the Linux kernel version from 1252 | # appearing as the 'best' version on otherwise 1253 | # identifiable distributions. 1254 | if name == "Linux": 1255 | return {} 1256 | props["id"] = name.lower() 1257 | props["name"] = name 1258 | props["release"] = version 1259 | return props 1260 | 1261 | @staticmethod 1262 | def _to_str(bytestring: bytes) -> str: 1263 | encoding = sys.getfilesystemencoding() 1264 | return bytestring.decode(encoding) 1265 | 1266 | @cached_property 1267 | def _distro_release_info(self) -> Dict[str, str]: 1268 | """ 1269 | Get the information items from the specified distro release file. 1270 | 1271 | Returns: 1272 | A dictionary containing all information items. 1273 | """ 1274 | if self.distro_release_file: 1275 | # If it was specified, we use it and parse what we can, even if 1276 | # its file name or content does not match the expected pattern. 1277 | distro_info = self._parse_distro_release_file(self.distro_release_file) 1278 | basename = os.path.basename(self.distro_release_file) 1279 | # The file name pattern for user-specified distro release files 1280 | # is somewhat more tolerant (compared to when searching for the 1281 | # file), because we want to use what was specified as best as 1282 | # possible. 1283 | match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename) 1284 | else: 1285 | try: 1286 | basenames = [ 1287 | basename 1288 | for basename in os.listdir(self.etc_dir) 1289 | if basename not in _DISTRO_RELEASE_IGNORE_BASENAMES 1290 | and os.path.isfile(os.path.join(self.etc_dir, basename)) 1291 | ] 1292 | # We sort for repeatability in cases where there are multiple 1293 | # distro specific files; e.g. CentOS, Oracle, Enterprise all 1294 | # containing `redhat-release` on top of their own. 1295 | basenames.sort() 1296 | except OSError: 1297 | # This may occur when /etc is not readable but we can't be 1298 | # sure about the *-release files. Check common entries of 1299 | # /etc for information. If they turn out to not be there the 1300 | # error is handled in `_parse_distro_release_file()`. 1301 | basenames = _DISTRO_RELEASE_BASENAMES 1302 | for basename in basenames: 1303 | match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename) 1304 | if match is None: 1305 | continue 1306 | filepath = os.path.join(self.etc_dir, basename) 1307 | distro_info = self._parse_distro_release_file(filepath) 1308 | # The name is always present if the pattern matches. 1309 | if "name" not in distro_info: 1310 | continue 1311 | self.distro_release_file = filepath 1312 | break 1313 | else: # the loop didn't "break": no candidate. 1314 | return {} 1315 | 1316 | if match is not None: 1317 | distro_info["id"] = match.group(1) 1318 | 1319 | # Armbian release files are not standard as they start with a 1320 | # comment. Manually set name if it has not been inferred. 1321 | if distro_info["id"] == "armbian" and distro_info.get( 1322 | "name", "" 1323 | ).startswith("#"): 1324 | distro_info["name"] = "Armbian" 1325 | distro_info["version_id"] = self._armbian_version 1326 | 1327 | # CloudLinux < 7: manually enrich info with proper id. 1328 | if "cloudlinux" in distro_info.get("name", "").lower(): 1329 | distro_info["id"] = "cloudlinux" 1330 | 1331 | return distro_info 1332 | 1333 | def _parse_distro_release_file(self, filepath: str) -> Dict[str, str]: 1334 | """ 1335 | Parse a distro release file. 1336 | 1337 | Parameters: 1338 | 1339 | * filepath: Path name of the distro release file. 1340 | 1341 | Returns: 1342 | A dictionary containing all information items. 1343 | """ 1344 | try: 1345 | with open(filepath, encoding="utf-8") as fp: 1346 | # Only parse the first line. For instance, on SLES there 1347 | # are multiple lines. We don't want them... 1348 | return self._parse_distro_release_content(fp.readline()) 1349 | except OSError: 1350 | # Ignore not being able to read a specific, seemingly version 1351 | # related file. 1352 | # See https://github.com/python-distro/distro/issues/162 1353 | return {} 1354 | 1355 | @staticmethod 1356 | def _parse_distro_release_content(line: str) -> Dict[str, str]: 1357 | """ 1358 | Parse a line from a distro release file. 1359 | 1360 | Parameters: 1361 | * line: Line from the distro release file. Must be a unicode string 1362 | or a UTF-8 encoded byte string. 1363 | 1364 | Returns: 1365 | A dictionary containing all information items. 1366 | """ 1367 | matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match(line.strip()[::-1]) 1368 | distro_info = {} 1369 | if matches: 1370 | # regexp ensures non-None 1371 | distro_info["name"] = matches.group(3)[::-1] 1372 | if matches.group(2): 1373 | distro_info["version_id"] = matches.group(2)[::-1] 1374 | if matches.group(1): 1375 | distro_info["codename"] = matches.group(1)[::-1] 1376 | elif line: 1377 | distro_info["name"] = line.strip() 1378 | return distro_info 1379 | 1380 | 1381 | _distro = LinuxDistribution() 1382 | 1383 | 1384 | def main() -> None: 1385 | logger = logging.getLogger(__name__) 1386 | logger.setLevel(logging.DEBUG) 1387 | logger.addHandler(logging.StreamHandler(sys.stdout)) 1388 | 1389 | parser = argparse.ArgumentParser(description="OS distro info tool") 1390 | parser.add_argument( 1391 | "--json", "-j", help="Output in machine readable format", action="store_true" 1392 | ) 1393 | 1394 | parser.add_argument( 1395 | "--root-dir", 1396 | "-r", 1397 | type=str, 1398 | dest="root_dir", 1399 | help="Path to the root filesystem directory (defaults to /)", 1400 | ) 1401 | 1402 | args = parser.parse_args() 1403 | 1404 | if args.root_dir: 1405 | dist = LinuxDistribution( 1406 | include_lsb=False, 1407 | include_uname=False, 1408 | include_oslevel=False, 1409 | root_dir=args.root_dir, 1410 | ) 1411 | else: 1412 | dist = _distro 1413 | 1414 | if args.json: 1415 | logger.info(json.dumps(dist.info(), indent=4, sort_keys=True)) 1416 | else: 1417 | logger.info("Name: %s", dist.name(pretty=True)) 1418 | distribution_version = dist.version(pretty=True) 1419 | logger.info("Version: %s", distribution_version) 1420 | distribution_codename = dist.codename() 1421 | logger.info("Codename: %s", distribution_codename) 1422 | 1423 | 1424 | if __name__ == "__main__": 1425 | main() 1426 | -------------------------------------------------------------------------------- /src/distro/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-distro/distro/3bd19e61fcb7f8d2bf3d45d9e40d69c92e05d241/src/distro/py.typed -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-distro/distro/3bd19e61fcb7f8d2bf3d45d9e40d69c92e05d241/tests/__init__.py -------------------------------------------------------------------------------- /tests/resources/cli/fedora30/etc/fedora-release: -------------------------------------------------------------------------------- 1 | Fedora release 30 (Thirty) 2 | -------------------------------------------------------------------------------- /tests/resources/cli/fedora30/etc/os-release: -------------------------------------------------------------------------------- 1 | NAME=Fedora 2 | VERSION="30 (Thirty)" 3 | ID=fedora 4 | VERSION_ID=30 5 | VERSION_CODENAME="" 6 | PLATFORM_ID="platform:f30" 7 | PRETTY_NAME="Fedora 30 (Thirty)" 8 | ANSI_COLOR="0;34" 9 | LOGO=fedora-logo-icon 10 | CPE_NAME="cpe:/o:fedoraproject:fedora:30" 11 | HOME_URL="https://fedoraproject.org/" 12 | DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/30/system-administrators-guide/" 13 | SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help" 14 | BUG_REPORT_URL="https://bugzilla.redhat.com/" 15 | REDHAT_BUGZILLA_PRODUCT="Fedora" 16 | REDHAT_BUGZILLA_PRODUCT_VERSION=30 17 | REDHAT_SUPPORT_PRODUCT="Fedora" 18 | REDHAT_SUPPORT_PRODUCT_VERSION=30 19 | PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy" 20 | -------------------------------------------------------------------------------- /tests/resources/cli/fedora30/etc/redhat-release: -------------------------------------------------------------------------------- 1 | Fedora release 30 (Thirty) 2 | -------------------------------------------------------------------------------- /tests/resources/cli/fedora30/etc/system-release: -------------------------------------------------------------------------------- 1 | Fedora release 30 (Thirty) 2 | -------------------------------------------------------------------------------- /tests/resources/distros/__shared__/bin/lsb_release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # lsb_release command for testing the ld module. 4 | # Only the -a option is supported. 5 | # 6 | # This version of the lsb_release command reads an lsb-release file. 7 | # 8 | # The lsb-release file has the usual format, e.g.: 9 | # DISTRIB_ID=Ubuntu 10 | # DISTRIB_RELEASE=14.04 11 | # DISTRIB_CODENAME=trusty 12 | # DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS" 13 | # Where each line is optional. If a line is missing, the default value 14 | # will be the empty string. 15 | # 16 | 17 | if [[ "$@" != "-a" ]]; then 18 | echo "Usage: lsb_release -a" 19 | exit 2 20 | fi 21 | 22 | # Because the PATH is set to just this directory, we cannot use 'dirname' 23 | # or other external programs, but need to use built-in abilities of bash. 24 | LSB_FILE="${0%/*}/../etc/lsb-release" 25 | 26 | if [[ ! -f $LSB_FILE ]]; then 27 | echo "Error: LSB release file does not exist: $LSB_FILE" 28 | exit 1 29 | fi 30 | 31 | source $LSB_FILE 32 | 33 | if [[ -n $LSB_VERSION ]]; then 34 | echo "LSB Version: $LSB_VERSION" 35 | else 36 | echo "No LSB modules are available." 37 | fi 38 | echo "Distributor ID: ${DISTRIB_ID:-}" 39 | echo "Description: ${DISTRIB_DESCRIPTION:-}" 40 | echo "Release: ${DISTRIB_RELEASE:-}" 41 | echo "Codename: ${DISTRIB_CODENAME:-}" 42 | 43 | exit 0 44 | -------------------------------------------------------------------------------- /tests/resources/distros/aix72/bin/oslevel: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "7.2.0.0" 4 | -------------------------------------------------------------------------------- /tests/resources/distros/aix72/bin/uname: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "AIX 2" 4 | -------------------------------------------------------------------------------- /tests/resources/distros/altlinux10/etc/altlinux-release: -------------------------------------------------------------------------------- 1 | ALT Server 10.1 (Mendelevium) 2 | -------------------------------------------------------------------------------- /tests/resources/distros/altlinux10/etc/fedora-release: -------------------------------------------------------------------------------- 1 | altlinux-release -------------------------------------------------------------------------------- /tests/resources/distros/altlinux10/etc/os-release: -------------------------------------------------------------------------------- 1 | NAME="ALT Server" 2 | VERSION="10.1" 3 | ID=altlinux 4 | VERSION_ID=10.1 5 | PRETTY_NAME="ALT Server 10.1 (Mendelevium)" 6 | ANSI_COLOR="1;33" 7 | CPE_NAME="cpe:/o:alt:server:10.1" 8 | BUILD_ID="ALT Server 10.1" 9 | HOME_URL="https://basealt.ru/" 10 | BUG_REPORT_URL="https://bugs.altlinux.org/" 11 | -------------------------------------------------------------------------------- /tests/resources/distros/altlinux10/etc/redhat-release: -------------------------------------------------------------------------------- 1 | altlinux-release -------------------------------------------------------------------------------- /tests/resources/distros/altlinux10/etc/system-release: -------------------------------------------------------------------------------- 1 | altlinux-release -------------------------------------------------------------------------------- /tests/resources/distros/amazon2014/etc/system-release: -------------------------------------------------------------------------------- 1 | Amazon Linux AMI release 2014.03 -------------------------------------------------------------------------------- /tests/resources/distros/amazon2016/etc/os-release: -------------------------------------------------------------------------------- 1 | NAME="Amazon Linux AMI" 2 | VERSION="2016.03" 3 | ID="amzn" 4 | ID_LIKE="rhel fedora" 5 | VERSION_ID="2016.03" 6 | PRETTY_NAME="Amazon Linux AMI 2016.03" 7 | ANSI_COLOR="0;33" 8 | CPE_NAME="cpe:/o:amazon:linux:2016.03:ga" 9 | HOME_URL="http://aws.amazon.com/amazon-linux-ami/" 10 | -------------------------------------------------------------------------------- /tests/resources/distros/amazon2016/etc/system-release: -------------------------------------------------------------------------------- 1 | Amazon Linux AMI release 2016.03 2 | -------------------------------------------------------------------------------- /tests/resources/distros/arch/etc/arch-release: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-distro/distro/3bd19e61fcb7f8d2bf3d45d9e40d69c92e05d241/tests/resources/distros/arch/etc/arch-release -------------------------------------------------------------------------------- /tests/resources/distros/arch/etc/os-release: -------------------------------------------------------------------------------- 1 | ../usr/lib/os-release -------------------------------------------------------------------------------- /tests/resources/distros/arch/usr/lib/os-release: -------------------------------------------------------------------------------- 1 | NAME="Arch Linux" 2 | PRETTY_NAME="Arch Linux" 3 | ID=arch 4 | BUILD_ID=rolling 5 | ANSI_COLOR="38;2;23;147;209" 6 | HOME_URL="https://archlinux.org/" 7 | DOCUMENTATION_URL="https://wiki.archlinux.org/" 8 | SUPPORT_URL="https://bbs.archlinux.org/" 9 | BUG_REPORT_URL="https://bugs.archlinux.org/" 10 | PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/" 11 | LOGO=archlinux-logo 12 | -------------------------------------------------------------------------------- /tests/resources/distros/armbian/etc/armbian-release: -------------------------------------------------------------------------------- 1 | # PLEASE DO NOT EDIT THIS FILE 2 | BOARD=nanopim4v2 3 | BOARD_NAME="NanoPi M4V2" 4 | BOARDFAMILY=rk3399 5 | BUILD_REPOSITORY_URL=https://github.com/armbian/build 6 | BUILD_REPOSITORY_COMMIT=1a8daf0 7 | VERSION=23.02.2 8 | LINUXFAMILY=rockchip64 9 | ARCH=arm64 10 | IMAGE_TYPE=nightly 11 | BOARD_TYPE=conf 12 | INITRD_ARCH=arm64 13 | KERNEL_IMAGE_TYPE=Image 14 | BRANCH=current -------------------------------------------------------------------------------- /tests/resources/distros/armbian/etc/os-release: -------------------------------------------------------------------------------- 1 | ../usr/lib/os-release -------------------------------------------------------------------------------- /tests/resources/distros/armbian/usr/lib/os-release: -------------------------------------------------------------------------------- 1 | PRETTY_NAME="Debian GNU/Linux 10 (buster)" 2 | NAME="Debian GNU/Linux" 3 | VERSION_ID="10" 4 | VERSION="10 (buster)" 5 | VERSION_CODENAME=buster 6 | ID=debian 7 | HOME_URL="https://www.debian.org/" 8 | SUPPORT_URL="https://www.debian.org/support" 9 | BUG_REPORT_URL="https://bugs.debian.org/" 10 | -------------------------------------------------------------------------------- /tests/resources/distros/bttcb1/etc/board-release: -------------------------------------------------------------------------------- 1 | # PLEASE DO NOT EDIT THIS FILE 2 | BOARD=h616 3 | BOARD_NAME="BTT-CB1" 4 | DISTRIBUTION_CODENAME=bullseye 5 | VERSION=2.3.1 6 | LINUXFAMILY=sun50iw9 7 | ARCH=arm64 8 | INITRD_ARCH=arm64 9 | BRANCH=current -------------------------------------------------------------------------------- /tests/resources/distros/bttcb1/etc/os-release: -------------------------------------------------------------------------------- 1 | PRETTY_NAME="BTT-CB1 2.3.1 Bullseye" 2 | NAME="Debian GNU/Linux" 3 | VERSION_ID="11" 4 | VERSION="11 (bullseye)" 5 | VERSION_CODENAME=bullseye 6 | ID=debian 7 | HOME_URL="https://www.debian.org/" 8 | SUPPORT_URL="https://www.debian.org/support" 9 | BUG_REPORT_URL="https://bugs.debian.org/" -------------------------------------------------------------------------------- /tests/resources/distros/bttcb1/usr/lib/os-release: -------------------------------------------------------------------------------- 1 | PRETTY_NAME="Debian GNU/Linux 11 (bullseye)" 2 | NAME="Debian GNU/Linux" 3 | VERSION_ID="11" 4 | VERSION="11 (bullseye)" 5 | VERSION_CODENAME=bullseye 6 | ID=debian 7 | HOME_URL="https://www.debian.org/" 8 | SUPPORT_URL="https://www.debian.org/support" 9 | BUG_REPORT_URL="https://bugs.debian.org/" -------------------------------------------------------------------------------- /tests/resources/distros/buildroot/etc/os-release: -------------------------------------------------------------------------------- 1 | NAME=Buildroot 2 | VERSION=2022.02 3 | ID=buildroot 4 | VERSION_ID=2022.02 5 | PRETTY_NAME="Buildroot 2022.02" 6 | -------------------------------------------------------------------------------- /tests/resources/distros/centos5/etc/centos-release: -------------------------------------------------------------------------------- 1 | CentOS release 5.11 (Final) 2 | -------------------------------------------------------------------------------- /tests/resources/distros/centos5/etc/redhat-release: -------------------------------------------------------------------------------- 1 | centos-release -------------------------------------------------------------------------------- /tests/resources/distros/centos5/etc/system-release: -------------------------------------------------------------------------------- 1 | centos-release -------------------------------------------------------------------------------- /tests/resources/distros/centos7/etc/centos-release: -------------------------------------------------------------------------------- 1 | CentOS Linux release 7.1.1503 (Core) 2 | -------------------------------------------------------------------------------- /tests/resources/distros/centos7/etc/os-release: -------------------------------------------------------------------------------- 1 | NAME="CentOS Linux" 2 | VERSION="7 (Core)" 3 | ID="centos" 4 | ID_LIKE="rhel fedora" 5 | VERSION_ID="7" 6 | PRETTY_NAME="CentOS Linux 7 (Core)" 7 | ANSI_COLOR="0;31" 8 | CPE_NAME="cpe:/o:centos:centos:7" 9 | HOME_URL="https://www.centos.org/" 10 | BUG_REPORT_URL="https://bugs.centos.org/" 11 | 12 | CENTOS_MANTISBT_PROJECT="CentOS-7" 13 | CENTOS_MANTISBT_PROJECT_VERSION="7" 14 | REDHAT_SUPPORT_PRODUCT="centos" 15 | REDHAT_SUPPORT_PRODUCT_VERSION="7" 16 | 17 | -------------------------------------------------------------------------------- /tests/resources/distros/centos7/etc/redhat-release: -------------------------------------------------------------------------------- 1 | centos-release -------------------------------------------------------------------------------- /tests/resources/distros/centos7/etc/system-release: -------------------------------------------------------------------------------- 1 | centos-release -------------------------------------------------------------------------------- /tests/resources/distros/cloudlinux5/etc/redhat-release: -------------------------------------------------------------------------------- 1 | CloudLinux Server release 5.11 (Vladislav Volkov) 2 | -------------------------------------------------------------------------------- /tests/resources/distros/cloudlinux6/etc/redhat-release: -------------------------------------------------------------------------------- 1 | CloudLinux Server release 6.8 (Oleg Makarov) 2 | -------------------------------------------------------------------------------- /tests/resources/distros/cloudlinux7/etc/os-release: -------------------------------------------------------------------------------- 1 | NAME="CloudLinux" 2 | VERSION="7.3 (Yury Malyshev)" 3 | ID="cloudlinux" 4 | ID_LIKE="rhel fedora centos" 5 | VERSION_ID="7.3" 6 | PRETTY_NAME="CloudLinux 7.3 (Yury Malyshev)" 7 | ANSI_COLOR="0:31" 8 | CPE_NAME="cpe:/o:cloudlinux:cloudlinux:7.3:GA:server" 9 | HOME_URL="https://www.cloudlinux.com/" 10 | BUG_REPORT_URL="https://helpdesk.cloudlinux.com/" 11 | -------------------------------------------------------------------------------- /tests/resources/distros/cloudlinux7/etc/redhat-release: -------------------------------------------------------------------------------- 1 | CloudLinux release 7.3 (Yury Malyshev) 2 | -------------------------------------------------------------------------------- /tests/resources/distros/coreos/etc/oem-release: -------------------------------------------------------------------------------- 1 | ID=digitalocean 2 | VERSION_ID=0.0.4 3 | NAME="DigitalOcean" 4 | HOME_URL="https://www.digitalocean.com/" 5 | BUG_REPORT_URL="https://github.com/coreos/bugs/issues" -------------------------------------------------------------------------------- /tests/resources/distros/coreos/etc/os-release: -------------------------------------------------------------------------------- 1 | NAME=CoreOS 2 | ID=coreos 3 | VERSION=899.15.0 4 | VERSION_ID=899.15.0 5 | BUILD_ID=2016-04-05-1035 6 | PRETTY_NAME="CoreOS 899.15.0" 7 | ANSI_COLOR="1;32" 8 | HOME_URL="https://coreos.com/" 9 | BUG_REPORT_URL="https://github.com/coreos/bugs/issues" -------------------------------------------------------------------------------- /tests/resources/distros/debian10/bin/lsb_release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # lsb_release command for testing the ld module. 4 | # Only the -a option is supported. 5 | # 6 | # This version of the lsb_release command works without a corresponding 7 | # etc/lsb-release file. 8 | # 9 | 10 | if [[ "$@" != "-a" ]]; then 11 | echo "Usage: lsb_release -a" 12 | exit 2 13 | fi 14 | 15 | echo "No LSB modules are available." 16 | echo "Distributor ID: Debian" 17 | echo "Description: Debian GNU/Linux 10 (buster)" 18 | echo "Release: 10" 19 | echo "Codename: buster" 20 | 21 | exit 0 22 | -------------------------------------------------------------------------------- /tests/resources/distros/debian10/etc/debian_version: -------------------------------------------------------------------------------- 1 | 10.11 2 | -------------------------------------------------------------------------------- /tests/resources/distros/debian10/etc/os-release: -------------------------------------------------------------------------------- 1 | PRETTY_NAME="Debian GNU/Linux 10 (buster)" 2 | NAME="Debian GNU/Linux" 3 | VERSION_ID="10" 4 | VERSION="10 (buster)" 5 | VERSION_CODENAME=buster 6 | ID=debian 7 | HOME_URL="https://www.debian.org/" 8 | SUPPORT_URL="https://www.debian.org/support" 9 | BUG_REPORT_URL="https://bugs.debian.org/" 10 | -------------------------------------------------------------------------------- /tests/resources/distros/debian8/bin/lsb_release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # lsb_release command for testing the ld module. 4 | # Only the -a option is supported. 5 | # 6 | # This version of the lsb_release command works without a corresponding 7 | # etc/lsb-release file. 8 | # 9 | 10 | if [[ "$@" != "-a" ]]; then 11 | echo "Usage: lsb_release -a" 12 | exit 2 13 | fi 14 | 15 | echo "No LSB modules are available." 16 | echo "Distributor ID: Debian" 17 | echo "Description: Debian GNU/Linux 8.2 (jessie)" 18 | echo "Release: 8.2" 19 | echo "Codename: jessie" 20 | 21 | exit 0 22 | -------------------------------------------------------------------------------- /tests/resources/distros/debian8/etc/debian_version: -------------------------------------------------------------------------------- 1 | 8.2 -------------------------------------------------------------------------------- /tests/resources/distros/debian8/etc/os-release: -------------------------------------------------------------------------------- 1 | PRETTY_NAME="Debian GNU/Linux 8 (jessie)" 2 | NAME="Debian GNU/Linux" 3 | VERSION_ID="8" 4 | VERSION="8 (jessie)" 5 | ID=debian 6 | HOME_URL="http://www.debian.org/" 7 | SUPPORT_URL="http://www.debian.org/support/" 8 | BUG_REPORT_URL="https://bugs.debian.org/" 9 | -------------------------------------------------------------------------------- /tests/resources/distros/debiantesting/bin/lsb_release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # lsb_release command for testing the ld module. 4 | # Only the -a option is supported. 5 | # 6 | # This version of the lsb_release command works without a corresponding 7 | # etc/lsb-release file. 8 | # 9 | 10 | if [[ "$@" != "-a" ]]; then 11 | echo "Usage: lsb_release -a" 12 | exit 2 13 | fi 14 | 15 | cat <