Compare commits
21 Commits
Author | SHA1 | Date |
---|---|---|
Lucidiot | 1f7cac1d8c | |
Lucidiot | ac6a1b3103 | |
~lucidiot | 04e3354394 | |
~lucidiot | 91918e8d41 | |
~lucidiot | e3a89613e1 | |
~lucidiot | 4438f17e94 | |
~lucidiot | 70b155cf04 | |
~lucidiot | d6becffcc0 | |
~lucidiot | 27cc5de1b9 | |
~lucidiot | 1936c58dec | |
~lucidiot | 544edd0b3d | |
~lucidiot | da39b09ef5 | |
~lucidiot | 149b3bde99 | |
~lucidiot | ad52e4c823 | |
~lucidiot | 30e1ed5ec4 | |
~lucidiot | 7cebd6202d | |
~lucidiot | 57d96ab345 | |
~lucidiot | 386b7fc326 | |
~lucidiot | 587a55af73 | |
~lucidiot | 96cbe0e72c | |
~lucidiot | 09256806f1 |
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: pre-commit
|
||||
image: python:3-alpine
|
||||
commands:
|
||||
- apk add --no-cache git gcc musl-dev
|
||||
- pip install .[dev]
|
||||
- pre-commit run -a
|
||||
|
||||
- name: test-py36
|
||||
image: python:3.6-alpine
|
||||
commands:
|
||||
- pip install .[dev]
|
||||
- coverage run setup.py test
|
||||
- coverage report
|
||||
|
||||
- name: test-py37
|
||||
image: python:3.7-alpine
|
||||
commands:
|
||||
- pip install .[dev]
|
||||
- coverage run setup.py test
|
||||
- coverage report
|
||||
|
||||
- name: test-py38
|
||||
image: python:3.8-alpine
|
||||
commands:
|
||||
- pip install .[dev]
|
||||
- coverage run setup.py test
|
||||
- coverage report
|
||||
|
||||
- name: test-py39
|
||||
image: python:3.9-alpine
|
||||
commands:
|
||||
- pip install .[dev]
|
||||
- coverage run setup.py test
|
||||
- coverage report
|
||||
|
||||
- name: test-py310
|
||||
image: python:3.10-alpine
|
||||
commands:
|
||||
- pip install .[dev]
|
||||
- coverage run setup.py test
|
||||
- coverage report
|
||||
|
||||
- name: test-py311
|
||||
image: python:3.11-alpine
|
||||
commands:
|
||||
- pip install .[dev]
|
||||
- coverage run setup.py test
|
||||
- coverage report
|
||||
|
||||
- name: testpypi
|
||||
image: python:3.11-alpine
|
||||
commands:
|
||||
- pip install .[dev] twine setuptools wheel
|
||||
- |
|
||||
echo "[distutils]
|
||||
index-servers = testpypi
|
||||
[testpypi]
|
||||
repository=https://test.pypi.org/legacy/
|
||||
username=$$TESTPYPI_DEPLOY_USERNAME
|
||||
password=$$TESTPYPI_DEPLOY_PASSWORD" > ~/.pypirc
|
||||
- python setup.py sdist bdist_wheel
|
||||
- twine upload dist/* -r testpypi
|
||||
|
||||
when:
|
||||
event:
|
||||
- promote
|
||||
repo:
|
||||
- lucidiot/pylspci
|
||||
|
||||
depends_on:
|
||||
- pre-commit
|
||||
- test-py36
|
||||
- test-py37
|
||||
- test-py38
|
||||
- test-py39
|
||||
- test-py310
|
||||
- test-py311
|
||||
|
||||
environment:
|
||||
TESTPYPI_DEPLOY_USERNAME:
|
||||
from_secret: testpypi_username
|
||||
TESTPYPI_DEPLOY_PASSWORD:
|
||||
from_secret: testpypi_password
|
||||
|
||||
- name: pypi
|
||||
image: python:3.11-alpine
|
||||
commands:
|
||||
- pip install .[dev] twine setuptools wheel
|
||||
- |
|
||||
echo "[distutils]
|
||||
index-servers = pypi
|
||||
[pypi]
|
||||
repository=https://upload.pypi.org/legacy/
|
||||
username=$$PYPI_DEPLOY_USERNAME
|
||||
password=$$PYPI_DEPLOY_PASSWORD" > ~/.pypirc
|
||||
- python setup.py sdist bdist_wheel
|
||||
- twine upload dist/* -r pypi
|
||||
|
||||
when:
|
||||
event:
|
||||
- promote
|
||||
repo:
|
||||
- lucidiot/pylspci
|
||||
branch:
|
||||
- master
|
||||
|
||||
depends_on:
|
||||
- testpypi
|
||||
|
||||
environment:
|
||||
PYPI_DEPLOY_USERNAME:
|
||||
from_secret: pypi_username
|
||||
PYPI_DEPLOY_PASSWORD:
|
||||
from_secret: pypi_password
|
|
@ -1,90 +0,0 @@
|
|||
image: python:3.7
|
||||
stages:
|
||||
- test
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .cache/pip
|
||||
- venv/
|
||||
|
||||
before_script:
|
||||
- pip install virtualenv
|
||||
- virtualenv venv
|
||||
- source venv/bin/activate
|
||||
- pip install .[dev]
|
||||
|
||||
tests:
|
||||
stage: test
|
||||
coverage: '/TOTAL[\s\d]+\s(\d+%)/'
|
||||
script:
|
||||
- coverage run setup.py test
|
||||
- coverage report
|
||||
- codecov
|
||||
|
||||
pre-commit:
|
||||
stage: test
|
||||
script:
|
||||
- pre-commit run -a
|
||||
|
||||
deploy-pypi:
|
||||
stage: deploy
|
||||
when: manual
|
||||
only:
|
||||
- master@Lucidiot/pylspci
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/project/pylspci
|
||||
|
||||
script:
|
||||
- pip install twine setuptools wheel
|
||||
- echo "[distutils]" > ~/.pypirc
|
||||
- echo "index-servers =" >> ~/.pypirc
|
||||
- echo " pypi" >> ~/.pypirc
|
||||
- echo "[pypi]" >> ~/.pypirc
|
||||
- echo "repository=https://upload.pypi.org/legacy/" >> ~/.pypirc
|
||||
- echo "username=$PYPI_DEPLOY_USERNAME" >> ~/.pypirc
|
||||
- echo "password=$PYPI_DEPLOY_PASSWORD" >> ~/.pypirc
|
||||
- python setup.py sdist bdist_wheel
|
||||
- twine upload dist/* -r pypi
|
||||
|
||||
deploy-testpypi:
|
||||
stage: deploy
|
||||
when: manual
|
||||
only:
|
||||
- branches@Lucidiot/pylspci
|
||||
environment:
|
||||
name: testpypi
|
||||
url: https://test.pypi.org/project/pylspci
|
||||
|
||||
script:
|
||||
- pip install twine setuptools wheel
|
||||
- echo "[distutils]" > ~/.pypirc
|
||||
- echo "index-servers =" >> ~/.pypirc
|
||||
- echo " testpypi" >> ~/.pypirc
|
||||
- echo "[testpypi]" >> ~/.pypirc
|
||||
- echo "repository=https://test.pypi.org/legacy/" >> ~/.pypirc
|
||||
- echo "username=$PYPI_DEPLOY_USERNAME" >> ~/.pypirc
|
||||
- echo "password=$PYPI_DEPLOY_PASSWORD" >> ~/.pypirc
|
||||
- python setup.py sdist bdist_wheel
|
||||
- twine upload dist/* -r testpypi
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
when: manual
|
||||
needs:
|
||||
- pre-commit
|
||||
only:
|
||||
- master@Lucidiot/pylspci
|
||||
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
|
||||
script:
|
||||
- cd docs
|
||||
- make html
|
||||
- mv _build/html ../public
|
|
@ -2,7 +2,7 @@
|
|||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
@ -11,12 +11,12 @@ repos:
|
|||
- id: check-merge-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-symlinks
|
||||
- repo: https://gitlab.com/PyCQA/flake8
|
||||
rev: 3.8.4
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.790
|
||||
rev: v1.2.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
args:
|
||||
|
@ -26,10 +26,10 @@ repos:
|
|||
- --check-untyped-defs
|
||||
- --no-implicit-optional
|
||||
- repo: https://github.com/PyCQA/doc8
|
||||
rev: 0.8.1
|
||||
rev: v1.1.1
|
||||
hooks:
|
||||
- id: doc8
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.6.4
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
include requirements.txt
|
||||
include requirements-dev.txt
|
||||
include VERSION
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include pylspci/py.typed
|
|
@ -5,4 +5,4 @@ A Python parser for the ``lspci`` command from the pciutils_ package.
|
|||
`Browse documentation`_
|
||||
|
||||
.. _pciutils: http://mj.ucw.cz/sw/pciutils/
|
||||
.. _Browse documentation: https://lucidiot.gitlab.io/pylspci/
|
||||
.. _Browse documentation: https://lucidiot.tildepages.org/pylspci/
|
||||
|
|
|
@ -21,7 +21,7 @@ sys.path.insert(0, os.path.abspath('..'))
|
|||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'pylspci'
|
||||
copyright = '2019, Lucidiot and contributors'
|
||||
copyright = '2022, Lucidiot and contributors'
|
||||
author = 'Lucidiot and contributors'
|
||||
|
||||
# The short X.Y version
|
||||
|
@ -62,7 +62,7 @@ master_doc = 'index'
|
|||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
language = 'en'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
|
|
|
@ -6,8 +6,8 @@ Contributions to the project are greatly appreciated.
|
|||
Bugs and suggestions
|
||||
--------------------
|
||||
|
||||
You may `submit an issue`_ to GitLab to warn of any bugs, ask for new features,
|
||||
or ask any questions that are not answered in this documentation.
|
||||
You may `submit an issue`_ to the Gitea repository to warn of any bugs, ask for
|
||||
new features, or ask any questions that are not answered in this documentation.
|
||||
|
||||
When reporting a bug, do not forget to put in your version of Python and your
|
||||
version of *pylspci*. This will greatly help when troubleshooting, as most
|
||||
|
@ -22,7 +22,7 @@ Setup
|
|||
You will need a virtual envionment to work properly. `virtualenvwrapper`_ is
|
||||
recommended::
|
||||
|
||||
git clone https://gitlab.com/Lucidiot/pylspci
|
||||
git clone https://tildegit.org/lucidiot/pylspci.git
|
||||
cd pylspci
|
||||
mkvirtualenv -a . pylspci
|
||||
pip install -e .[dev]
|
||||
|
@ -44,8 +44,8 @@ Tests coverage
|
|||
|
||||
I aim for 100% coverage on all of my Python packages whenever I add unit
|
||||
tests to them; this package is no exception. CI checks use the `coverage`_
|
||||
Python package and `codecov`_ to check for test coverage. To get test coverage
|
||||
data locally, run::
|
||||
Python package to get coverage statistics.
|
||||
To get test coverage data locally, run::
|
||||
|
||||
coverage run setup.py test
|
||||
|
||||
|
@ -60,7 +60,7 @@ offline using your favorite web browser and shows line by line coverage::
|
|||
|
||||
If you are having issues reaching 100% coverage, try to still add some tests,
|
||||
and mention your issues when creating a pull request to the
|
||||
`GitLab repository`_.
|
||||
`Gitea repository`_.
|
||||
|
||||
Linting
|
||||
^^^^^^^
|
||||
|
@ -82,13 +82,12 @@ Documentation
|
|||
The documentation you are reading is generated by the `Sphinx`_ tool.
|
||||
The text files that hold the documentation's contents are written in
|
||||
`reStructuredText`_ and are available under the ``/docs`` folder of the
|
||||
`GitLab repository`_.
|
||||
`Gitea repository`_.
|
||||
They are also subject to linting using the ``doc8`` tool.
|
||||
|
||||
.. _submit an issue: https://gitlab.com/Lucidiot/pylspci/issues/new
|
||||
.. _submit an issue: https://tildegit.org/lucidiot/pylspci/issues/new
|
||||
.. _virtualenvwrapper: https://virtualenvwrapper.readthedocs.io
|
||||
.. _coverage: https://coverage.readthedocs.io/
|
||||
.. _codecov: https://codecov.io/gl/Lucidiot/pylspci
|
||||
.. _GitLab repository: https://gitlab.com/Lucidiot/pylspci
|
||||
.. _Gitea repository: https://tildegit.org/lucidiot/pylspci
|
||||
.. _Sphinx: http://www.sphinx-doc.org/
|
||||
.. _reStructuredText: http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
|
||||
|
|
|
@ -18,14 +18,11 @@ Python lspci parser
|
|||
.. image:: https://img.shields.io/pypi/status/pylspci.svg
|
||||
:target: https://pypi.org/project/pylspci
|
||||
|
||||
.. image:: https://gitlab.com/Lucidiot/pylspci/badges/master/pipeline.svg
|
||||
:target: https://gitlab.com/Lucidiot/pylspci/pipelines
|
||||
.. image:: https://drone.tildegit.org/api/badges/lucidiot/pylspci/status.svg
|
||||
:target: https://drone.tildegit.org/api/badges/lucidiot/pylspci/status.svg
|
||||
|
||||
.. image:: https://codecov.io/gl/Lucidiot/pylspci/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gl/Lucidiot/pylspci
|
||||
|
||||
.. image:: https://img.shields.io/badge/badge%20count-8-brightgreen.svg
|
||||
:target: https://gitlab.com/Lucidiot/pylspci
|
||||
.. image:: https://img.shields.io/badge/badge%20count-7-brightgreen.svg
|
||||
:target: https://tildegit.org/lucidiot/pylspci
|
||||
|
||||
A Python parser for the ``lspci`` command from the pciutils_ package.
|
||||
|
||||
|
|
|
@ -161,8 +161,7 @@ def main() -> None:
|
|||
parser: argparse.ArgumentParser = get_parser()
|
||||
args: Dict[str, Any] = vars(parser.parse_args())
|
||||
|
||||
# Specific parsing required
|
||||
use_parser: bool = args.pop('json', True)
|
||||
json_output: bool = args.pop('json', True)
|
||||
kernel_modules: bool = args.pop('kernel_modules', False)
|
||||
access_method: Optional[str] = args.pop('access_method', None)
|
||||
pcilib_params = args.pop('pcilib_params', []) or []
|
||||
|
@ -179,7 +178,7 @@ def main() -> None:
|
|||
|
||||
for param in pcilib_params:
|
||||
if param.strip().lower() == 'help':
|
||||
builder = builder.list_pcilib_params(raw=not use_parser)
|
||||
builder = builder.list_pcilib_params(raw=not json_output)
|
||||
break
|
||||
if '=' not in param:
|
||||
parser.error(
|
||||
|
@ -187,24 +186,19 @@ def main() -> None:
|
|||
key, value = map(str.strip, param.split('=', 2))
|
||||
builder = builder.with_pcilib_params(**{key: value})
|
||||
|
||||
if use_parser:
|
||||
if json_output:
|
||||
builder = builder.with_default_parser()
|
||||
|
||||
result = list(builder)
|
||||
if not use_parser: # Raw mode
|
||||
if not json_output: # Raw mode
|
||||
for item in result:
|
||||
print(item)
|
||||
return
|
||||
|
||||
def _item_handler(item: Any) -> Any:
|
||||
if hasattr(item, '_asdict'):
|
||||
return item._asdict()
|
||||
return item
|
||||
|
||||
print(json.dumps(
|
||||
list(map(_item_handler, result)),
|
||||
default=vars,
|
||||
))
|
||||
print(json.dumps([
|
||||
item if isinstance(item, str) else item.as_dict()
|
||||
for item in result
|
||||
]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
from typing import List, NamedTuple, Optional
|
||||
from typing import Dict, List, NamedTuple, Optional, Union
|
||||
|
||||
from pylspci.fields import NameWithID, Slot
|
||||
from pylspci.fields import NameWithID, NameWithIDDict, Slot, SlotDict
|
||||
|
||||
DeviceDict = Dict[str, Union[
|
||||
int,
|
||||
str,
|
||||
SlotDict,
|
||||
NameWithIDDict,
|
||||
List[str],
|
||||
None,
|
||||
]]
|
||||
|
||||
|
||||
class Device(NamedTuple):
|
||||
|
@ -11,90 +20,92 @@ class Device(NamedTuple):
|
|||
slot: Slot
|
||||
"""
|
||||
The device's slot (domain, bus, number and function).
|
||||
|
||||
:type: Slot
|
||||
"""
|
||||
|
||||
cls: NameWithID
|
||||
"""
|
||||
The device's class, with a name and/or an ID.
|
||||
|
||||
:type: NameWithID
|
||||
"""
|
||||
|
||||
vendor: NameWithID
|
||||
"""
|
||||
The device's vendor, with a name and/or an ID.
|
||||
|
||||
:type: NameWithID
|
||||
"""
|
||||
|
||||
device: NameWithID
|
||||
"""
|
||||
The device's name and/or ID.
|
||||
|
||||
:type: NameWithID
|
||||
"""
|
||||
|
||||
subsystem_vendor: Optional[NameWithID] = None
|
||||
"""
|
||||
The device's subsystem vendor, if found, with a name and/or an ID.
|
||||
|
||||
:type: NameWithID or None
|
||||
"""
|
||||
|
||||
subsystem_device: Optional[NameWithID] = None
|
||||
"""
|
||||
The device's subsystem name and/or ID, if found.
|
||||
|
||||
:type: NameWithID or None
|
||||
"""
|
||||
|
||||
revision: Optional[int] = None
|
||||
"""
|
||||
The device's revision number.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
progif: Optional[int] = None
|
||||
"""
|
||||
The device's programming interface number.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
driver: Optional[str] = None
|
||||
"""
|
||||
The device's driver (Linux only).
|
||||
|
||||
:type: str or None
|
||||
"""
|
||||
|
||||
kernel_modules: List[str] = []
|
||||
"""
|
||||
One or more kernel modules that can handle this device (Linux only).
|
||||
|
||||
:type: List[str] or None
|
||||
"""
|
||||
|
||||
numa_node: Optional[int] = None
|
||||
"""
|
||||
NUMA node this device is connected to (Linux only).
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
iommu_group: Optional[int] = None
|
||||
"""
|
||||
IOMMU group that this device is part of (optional, Linux only).
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
physical_slot: Optional[str] = None
|
||||
"""
|
||||
The device's physical slot number (Linux only).
|
||||
|
||||
:type: str or None
|
||||
"""
|
||||
|
||||
def as_dict(self) -> DeviceDict:
|
||||
"""
|
||||
Serialize this device as a JSON-serializable `dict`.
|
||||
"""
|
||||
return {
|
||||
"slot": self.slot.as_dict(),
|
||||
"cls": self.cls.as_dict(),
|
||||
"vendor": self.vendor.as_dict(),
|
||||
"device": self.device.as_dict(),
|
||||
"subsystem_vendor": (
|
||||
self.subsystem_vendor.as_dict()
|
||||
if self.subsystem_vendor
|
||||
else None
|
||||
),
|
||||
"subsystem_device": (
|
||||
self.subsystem_device.as_dict()
|
||||
if self.subsystem_device
|
||||
else None
|
||||
),
|
||||
"revision": self.revision,
|
||||
"progif": self.progif,
|
||||
"driver": self.driver,
|
||||
"kernel_modules": self.kernel_modules,
|
||||
"numa_node": self.numa_node,
|
||||
"iommu_group": self.iommu_group,
|
||||
"physical_slot": self.physical_slot,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import re
|
||||
from functools import partial
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
# mypy does not support recursive type definitions
|
||||
# SlotDict = Dict[str, Union[int, 'SlotDict', None]]
|
||||
SlotDict = Dict[str, Union[int, Dict[str, Any], None]]
|
||||
NameWithIDDict = Dict[str, Union[int, str, None]]
|
||||
|
||||
hexstring = partial(int, base=16)
|
||||
|
||||
|
@ -17,36 +22,26 @@ class Slot(object):
|
|||
"""
|
||||
The slot's domain, as a four-digit hexadecimal number.
|
||||
When omitted, defaults to ``0x0000``.
|
||||
|
||||
:type: int
|
||||
"""
|
||||
|
||||
bus: int
|
||||
"""
|
||||
The slot's bus, as a two-digit hexadecimal number.
|
||||
|
||||
:type: int
|
||||
"""
|
||||
|
||||
device: int
|
||||
"""
|
||||
The slot's device, as a two-digit hexadecimal number, up to `0x1f`.
|
||||
|
||||
:type: int
|
||||
"""
|
||||
|
||||
function: int
|
||||
"""
|
||||
The slot's function, as a single octal digit.
|
||||
|
||||
:type: int
|
||||
"""
|
||||
|
||||
parent: Optional["Slot"] = None
|
||||
"""
|
||||
The slot's parent bridge, if present.
|
||||
|
||||
:type: Slot or None
|
||||
"""
|
||||
|
||||
def __init__(self, value: str) -> None:
|
||||
|
@ -75,6 +70,18 @@ class Slot(object):
|
|||
def __repr__(self) -> str:
|
||||
return '{}({!r})'.format(self.__class__.__name__, str(self))
|
||||
|
||||
def as_dict(self) -> SlotDict:
|
||||
"""
|
||||
Serialize this slot as a JSON-serializable `dict`.
|
||||
"""
|
||||
return {
|
||||
"domain": self.domain,
|
||||
"bus": self.bus,
|
||||
"device": self.device,
|
||||
"function": self.function,
|
||||
"parent": self.parent.as_dict() if self.parent else None,
|
||||
}
|
||||
|
||||
|
||||
class NameWithID(object):
|
||||
"""
|
||||
|
@ -85,15 +92,11 @@ class NameWithID(object):
|
|||
id: Optional[int]
|
||||
"""
|
||||
The PCI ID as a four-digit hexadecimal number.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
name: Optional[str]
|
||||
"""
|
||||
The human-readable name associated with this ID.
|
||||
|
||||
:type: str or None
|
||||
"""
|
||||
|
||||
_NAME_ID_REGEX = re.compile(r'^(?P<name>.+)\s\[(?P<id>[0-9a-fA-F]{4})\]$')
|
||||
|
@ -131,6 +134,15 @@ class NameWithID(object):
|
|||
def __repr__(self) -> str:
|
||||
return '{}({!r})'.format(self.__class__.__name__, str(self))
|
||||
|
||||
def as_dict(self) -> NameWithIDDict:
|
||||
"""
|
||||
Serialize this name and ID as a JSON-serializable `dict`.
|
||||
"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
|
||||
class PCIAccessParameter(object):
|
||||
"""
|
||||
|
@ -142,22 +154,16 @@ class PCIAccessParameter(object):
|
|||
name: str
|
||||
"""
|
||||
The parameter's name.
|
||||
|
||||
:type: str
|
||||
"""
|
||||
|
||||
description: str
|
||||
"""
|
||||
A short description of the parameter's use.
|
||||
|
||||
:type: str
|
||||
"""
|
||||
|
||||
default: Optional[str]
|
||||
"""
|
||||
An optional default value for the parameter.
|
||||
|
||||
:type: str or None
|
||||
"""
|
||||
|
||||
_PARAM_REGEX = re.compile(
|
||||
|
@ -183,3 +189,13 @@ class PCIAccessParameter(object):
|
|||
return isinstance(other, PCIAccessParameter) and \
|
||||
(self.name, self.description, self.default) \
|
||||
== (other.name, other.description, other.default)
|
||||
|
||||
def as_dict(self) -> Dict[str, Optional[str]]:
|
||||
"""
|
||||
Serialize this PCI access parameter as a JSON-serializable `dict`.
|
||||
"""
|
||||
return {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"default": self.default,
|
||||
}
|
||||
|
|
|
@ -43,29 +43,21 @@ class SlotFilter(Filter):
|
|||
domain: Optional[int] = None
|
||||
"""
|
||||
Device domain, as a four-digit hexadecimal number.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
bus: Optional[int] = None
|
||||
"""
|
||||
Device bus, as a two-digit hexadecimal number.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
device: Optional[int] = None
|
||||
"""
|
||||
Device number, as a two-digit hexadecimal number, up to `0x1f`.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
function: Optional[int] = None
|
||||
"""
|
||||
The slot's function, as a single octal digit.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
# [[domain:]bus:][device][.function]
|
||||
|
@ -116,22 +108,16 @@ class DeviceFilter(Filter):
|
|||
cls: Optional[int] = None
|
||||
"""
|
||||
Device class ID, as a four-digit hexadecimal number.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
vendor: Optional[int] = None
|
||||
"""
|
||||
Device vendor ID, as a four-digit hexadecimal number.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
device: Optional[int] = None
|
||||
"""
|
||||
Device ID, as a four-digit hexadecimal number.
|
||||
|
||||
:type: int or None
|
||||
"""
|
||||
|
||||
# [vendor]:[device][:class]
|
||||
|
|
|
@ -8,7 +8,7 @@ from pylspci.parsers.base import Parser
|
|||
UNKNOWN_FIELD_WARNING = (
|
||||
'Unsupported device field {!r} with value {!r}\n'
|
||||
'Please report this, along with the output of `lspci -mmnnvvvk`, at '
|
||||
'https://gitlab.com/Lucidiot/pylspci/issues'
|
||||
'https://tildegit.org/lucidiot/pylspci/issues/new'
|
||||
)
|
||||
|
||||
|
||||
|
@ -21,23 +21,17 @@ class FieldMapping(NamedTuple):
|
|||
field_name: str
|
||||
"""
|
||||
Field name on the :class:`Device` named tuple.
|
||||
|
||||
:type: str
|
||||
"""
|
||||
|
||||
field_type: Callable[[str], Any]
|
||||
"""
|
||||
Field type; a callable to use to parse the string value.
|
||||
|
||||
:type: Callable[[str], Any]
|
||||
"""
|
||||
|
||||
many: bool = False
|
||||
"""
|
||||
Whether or not to use a List, if this field can be repeated multiple times
|
||||
in the lspci output.
|
||||
|
||||
:type: bool
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ class TestVerboseParser(TestCase):
|
|||
msg="Unsupported device field 'NewField' with value 'Value'\n"
|
||||
"Please report this, along with the output of"
|
||||
"`lspci -mmnnvvvk`, at "
|
||||
"https://gitlab.com/Lucidiot/pylspci/issues"):
|
||||
"https://tildegit.org/lucidiot/pylspci/issues/new"):
|
||||
devices: List[Device] = \
|
||||
self.parser.parse(SAMPLE_DEVICE + 'NewField\tValue')
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from pylspci.device import Device
|
||||
from pylspci.fields import NameWithID, Slot
|
||||
|
||||
|
||||
class TestDevice(TestCase):
|
||||
|
||||
def test_as_dict(self) -> None:
|
||||
d = Device(
|
||||
slot=Slot('cafe:13:07.2'),
|
||||
cls=NameWithID('Something [caf3]'),
|
||||
vendor=NameWithID('Something [caf3]'),
|
||||
device=NameWithID('Something [caf3]'),
|
||||
subsystem_vendor=NameWithID('Something [caf3]'),
|
||||
subsystem_device=NameWithID('Something [caf3]'),
|
||||
revision=20,
|
||||
progif=1,
|
||||
driver='self_driving',
|
||||
kernel_modules=['snd-pcsp'],
|
||||
numa_node=0,
|
||||
iommu_group=1,
|
||||
physical_slot='4-2',
|
||||
)
|
||||
self.assertDictEqual(d.as_dict(), {
|
||||
'slot': {
|
||||
'bus': 0x13,
|
||||
'device': 0x07,
|
||||
'domain': 0xcafe,
|
||||
'function': 0x2,
|
||||
'parent': None
|
||||
},
|
||||
'cls': {
|
||||
'id': 0xcaf3,
|
||||
'name': 'Something'
|
||||
},
|
||||
'vendor': {
|
||||
'id': 0xcaf3,
|
||||
'name': 'Something'
|
||||
},
|
||||
'device': {
|
||||
'id': 0xcaf3,
|
||||
'name': 'Something'
|
||||
},
|
||||
'subsystem_vendor': {
|
||||
'id': 0xcaf3,
|
||||
'name': 'Something'
|
||||
},
|
||||
'subsystem_device': {
|
||||
'id': 0xcaf3,
|
||||
'name': 'Something'
|
||||
},
|
||||
'revision': 20,
|
||||
'progif': 1,
|
||||
'driver': 'self_driving',
|
||||
'kernel_modules': ['snd-pcsp'],
|
||||
'numa_node': 0,
|
||||
'iommu_group': 1,
|
||||
'physical_slot': '4-2',
|
||||
})
|
|
@ -73,6 +73,16 @@ class TestSlot(TestCase):
|
|||
self.assertEqual(s.parent.parent.device, 0x07)
|
||||
self.assertEqual(s.parent.parent.function, 0x2)
|
||||
|
||||
def test_as_dict(self) -> None:
|
||||
s = Slot('cafe:13:07.2')
|
||||
self.assertDictEqual(s.as_dict(), {
|
||||
"domain": 0xcafe,
|
||||
"bus": 0x13,
|
||||
"device": 0x07,
|
||||
"function": 0x2,
|
||||
"parent": None,
|
||||
})
|
||||
|
||||
|
||||
class TestNameWithID(TestCase):
|
||||
|
||||
|
@ -128,6 +138,13 @@ class TestNameWithID(TestCase):
|
|||
self.assertIsNone(n.id)
|
||||
self.assertEqual(n.name, 'Something [hexa]')
|
||||
|
||||
def test_as_dict(self) -> None:
|
||||
n = NameWithID('Something [caf3]')
|
||||
self.assertDictEqual(n.as_dict(), {
|
||||
"id": 0xcaf3,
|
||||
"name": "Something",
|
||||
})
|
||||
|
||||
|
||||
class TestPCIAccessParameter(TestCase):
|
||||
|
||||
|
@ -159,3 +176,11 @@ class TestPCIAccessParameter(TestCase):
|
|||
p2 = PCIAccessParameter('param.name Some description ()')
|
||||
self.assertEqual(p1, p1)
|
||||
self.assertNotEqual(p1, p2)
|
||||
|
||||
def test_as_dict(self) -> None:
|
||||
p = PCIAccessParameter('param.name Some description (default value)')
|
||||
self.assertDictEqual(p.as_dict(), {
|
||||
"name": "param.name",
|
||||
"description": "Some description",
|
||||
"default": "default value",
|
||||
})
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
doc8>=0.8.0
|
||||
Sphinx>=1.8.1
|
||||
coverage>=4.5
|
||||
codecov>=2.0
|
||||
pre-commit>=2.9.2
|
||||
|
|
24
setup.py
24
setup.py
|
@ -22,22 +22,27 @@ setup(
|
|||
'console_scripts': ['pylspci=pylspci.__main__:main'],
|
||||
},
|
||||
package_data={
|
||||
'': ['VERSION', 'LICENSE', 'README.rst'],
|
||||
'': [
|
||||
'VERSION',
|
||||
'LICENSE',
|
||||
'README.rst',
|
||||
'requirements.txt',
|
||||
'requirements-dev.txt',
|
||||
],
|
||||
'pylspci': ['py.typed'],
|
||||
},
|
||||
python_requires='>=3.5',
|
||||
python_requires='>=3.6',
|
||||
install_requires=requirements,
|
||||
extras_require={
|
||||
'dev': dev_requirements,
|
||||
},
|
||||
tests_require=dev_requirements,
|
||||
test_suite='pylspci.tests',
|
||||
license='GNU General Public License 3',
|
||||
description="Simple parser for lspci -mmnn.",
|
||||
long_description=open('README.rst').read(),
|
||||
long_description_content_type='text/x-rst',
|
||||
keywords="lspci parser",
|
||||
url="https://gitlab.com/Lucidiot/pylspci",
|
||||
url="https://tildegit.org/lucidiot/pylspci",
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
|
@ -45,16 +50,21 @@ setup(
|
|||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: System :: Hardware",
|
||||
"Topic :: Utilities",
|
||||
"Typing :: Typed",
|
||||
],
|
||||
project_urls={
|
||||
"Source Code": "https://gitlab.com/Lucidiot/pylspci",
|
||||
"GitHub Mirror": "https://github.com/Lucidiot/pylspci",
|
||||
"Homepage": "https://tildegit.org/lucidiot/pylspci",
|
||||
"Changelog": "https://tildegit.org/lucidiot/pylspci/releases",
|
||||
"Documentation": "https://lucidiot.tildepages.org/pylspci/",
|
||||
"Issue tracker": "https://tildegit.org/lucidiot/pylspci/issues",
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue