Compare commits

...

36 Commits

Author SHA1 Message Date
Lucidiot 1f7cac1d8c
Update pre-commit hooks
continuous-integration/drone/push Build is passing Details
2023-05-09 13:06:37 +02:00
Lucidiot ac6a1b3103
Add Python 3.11 in CI
continuous-integration/drone Build is failing Details
2023-05-09 12:48:18 +02:00
~lucidiot 04e3354394
Split PyPI secrets
continuous-integration/drone/push Build is passing Details
2022-08-09 14:19:14 +02:00
~lucidiot 91918e8d41
Bump to 0.4.3
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
continuous-integration/drone Build is passing Details
2022-08-05 01:53:28 +02:00
~lucidiot e3a89613e1
Updates due to Tildegit move 2022-08-05 01:53:08 +02:00
~lucidiot 4438f17e94
Bump to 0.4.2 2022-02-08 22:44:56 +01:00
~lucidiot 70b155cf04
Fix missing files in source packages 2022-02-08 22:43:02 +01:00
~lucidiot d6becffcc0
Bump to 0.4.1 2022-01-26 19:19:40 +01:00
~lucidiot 27cc5de1b9 Merge branch 'manifest' into 'master'
Add MANIFEST.in

Closes #21

See merge request Lucidiot/pylspci!19
2022-01-26 18:19:26 +00:00
~lucidiot 1936c58dec
Add MANIFEST.in 2022-01-26 19:17:26 +01:00
~lucidiot 544edd0b3d
Bump to 0.4.0 2021-09-21 20:12:18 +02:00
~lucidiot da39b09ef5 Merge branch 'bump-python' into 'master'
Bump CI image to Python 3.9

See merge request Lucidiot/pylspci!18
2021-09-21 18:09:03 +00:00
~lucidiot 149b3bde99
Bump CI image to Python 3.9 2021-09-21 20:06:55 +02:00
~lucidiot ad52e4c823 Merge branch 'docs-cleanup' into 'master'
Remove unnecessary types in Sphinx docstrings

See merge request Lucidiot/pylspci!17
2021-09-21 18:05:37 +00:00
~lucidiot 30e1ed5ec4
Remove unnecessary types in Sphinx docstrings 2021-09-21 20:03:21 +02:00
~lucidiot 7cebd6202d Merge branch 'as-dict' into 'master'
Add dict serialization methods

Closes #20

See merge request Lucidiot/pylspci!16
2021-09-21 18:02:32 +00:00
~lucidiot 57d96ab345
Add dict serialization methods 2021-09-21 19:51:28 +02:00
~lucidiot 386b7fc326 Merge branch 'bump-pre-commit' into 'master'
Bump pre-commit hooks

See merge request Lucidiot/pylspci!15
2021-09-21 17:48:51 +00:00
~lucidiot 587a55af73
Bump pre-commit hooks 2021-09-21 19:46:45 +02:00
~lucidiot 96cbe0e72c Merge branch 'fix-setup' into 'master'
Fix setup.py test for Alpine build

See merge request Lucidiot/pylspci!14
2021-09-21 17:03:26 +00:00
~lucidiot 09256806f1
Fix setup.py test for Alpine build
This removes some unnecessary dependencies installed by setup.py test,
fixing an issue found in Alpine's package building CI:

https://gitlab.alpinelinux.org/Lucidiot/aports/-/jobs/492430
2021-09-21 01:35:01 +02:00
Lucidiot 27695dd747 Bump to 0.3.4 2021-01-26 17:28:44 +00:00
Lucidiot 0b00a2e586 Update physical_slot docstring 2021-01-26 17:27:20 +00:00
Lucidiot 06906411a1 Merge branch 'change_phy_slot_to_str' into 'master'
Changed physical_slot to str, because linux adds '-#' if duplicate slot numbers are found

See merge request Lucidiot/pylspci!13
2021-01-26 17:21:41 +00:00
Jan Lützler 9f90cf8519 Changed physical_slot to str, because linux adds '-#' if duplicate slot numbers are found 2021-01-26 17:21:41 +00:00
Lucidiot 7778e1a48a
Bump to 0.3.3 2020-11-29 19:55:19 +01:00
Lucidiot 51aac8692d
Update badge count 2020-11-29 19:54:56 +01:00
Lucidiot 11c57ae0f1
Remove dependency of pages deployment on unit tests 2020-11-29 19:54:10 +01:00
Lucidiot 7da6c0756c Merge branch 'add-verbose-error' into 'master'
Prevent using SimpleParser with verbose=True

Closes #18

See merge request Lucidiot/pylspci!12
2020-11-29 18:52:49 +00:00
Lucidiot 45dfdd7277
Prevent using SimpleParser with verbose=True 2020-11-29 19:45:48 +01:00
Lucidiot a34c08c9ce
Add isort 2020-11-29 19:17:36 +01:00
Lucidiot ce1abd76d0
Fix typing of kwargs 2020-11-29 19:06:57 +01:00
Lucidiot 0b06017ab7
Add pre-commit 2020-11-29 19:06:28 +01:00
Lucidiot 92417f1b6a
Fix intro examples 2020-11-29 18:02:13 +01:00
Lucidiot 65053cfdba
Remove GitHub badges 2020-11-29 18:00:37 +01:00
Lucidiot 3b93a30fb4
Add missing type checking docs 2020-11-29 17:59:56 +01:00
30 changed files with 473 additions and 282 deletions

120
.drone.yml Normal file
View File

@ -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

View File

@ -1,97 +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
flake8:
stage: test
script:
- flake8
mypy:
stage: test
script:
- mypy .
doc8:
stage: test
script:
- doc8
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
only:
- master@Lucidiot/pylspci
artifacts:
paths:
- public
script:
- cd docs
- make html
- mv _build/html ../public

35
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,35 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- id: check-executables-have-shebangs
- id: check-symlinks
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.2.0
hooks:
- id: mypy
args:
- --ignore-missing-imports
- --disallow-incomplete-defs
- --disallow-untyped-defs
- --check-untyped-defs
- --no-implicit-optional
- repo: https://github.com/PyCQA/doc8
rev: v1.1.1
hooks:
- id: doc8
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
include requirements.txt
include requirements-dev.txt
include VERSION
include LICENSE
include README.rst
include pylspci/py.typed

View File

@ -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/

View File

@ -1 +1 @@
0.3.2
0.4.3

View File

@ -16,4 +16,4 @@ help:
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -130,4 +130,3 @@ PCI access
``-H2``
Access hardware using Intel configuration mechanism 2.
Alias to ``-A intel-conf2``.

View File

@ -14,13 +14,14 @@
#
import os
import sys
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
@ -61,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.
@ -112,23 +113,23 @@ htmlhelp_basename = 'pylspcidoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
#
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
#
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
#
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
# }
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,

View File

@ -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
^^^^^^^
@ -69,19 +69,25 @@ The source code follows the PEP 8 code style and performs CI checks using the
``flake8`` tool. To perform the same checks locally, run ``flake8`` on the root
directory of this repository.
Type checking
^^^^^^^^^^^^^
The source code uses PEP 484 type hints and type checking is performed in CI
using ``mypy``. To run those checks locally, run ``mypy .`` on the root
directory of this repository.
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

View File

@ -18,20 +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://requires.io/github/Lucidiot/pylspci/requirements.svg?branch=master
:target: https://requires.io/github/Lucidiot/pylspci/requirements/?branch=master
.. image:: https://img.shields.io/github/last-commit/Lucidiot/pylspci.svg
:target: https://gitlab.com/Lucidiot/pylspci/commits
.. image:: https://img.shields.io/badge/badge%20count-10-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.
@ -63,7 +54,7 @@ To parse ``lspci -nnmmvvvk``, use the
.. code:: python
>>> from pylspci.parsers import SimpleParser
>>> SimpleParser.run()
>>> SimpleParser().run()
[Device(slot=Slot('0000:00:01.3'), name=NameWithID('Name A [2420]'), ...),
Device(slot=Slot('0000:00:01.4'), name=NameWithID('Name B [0e54]'), ...)]
@ -73,8 +64,8 @@ Custom arguments
.. code:: python
>>> from pylspci.command import IDResolveOption
>>> from pylspci import parser
>>> parser.run(
>>> from pylspci.parsers import VerboseParser
>>> VerboseParser().run(
... hide_single_domain=False,
... id_resolve_option=IDResolveOption.NameOnly,
... )

View File

@ -1,10 +1,11 @@
#!/usr/bin/env python3
from pathlib import Path
from typing import Optional, Dict, Any
from pylspci.command import CommandBuilder, IDResolveOption
from pylspci.filters import SlotFilter, DeviceFilter
import argparse
import json
from pathlib import Path
from typing import Any, Dict, Optional
from pylspci.command import CommandBuilder, IDResolveOption
from pylspci.filters import DeviceFilter, SlotFilter
def get_parser() -> argparse.ArgumentParser:
@ -160,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 []
@ -178,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(
@ -186,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__':

View File

@ -1,12 +1,14 @@
import subprocess
from enum import Enum
from typing import \
Optional, Union, List, Mapping, MutableMapping, Any, Iterator
from pathlib import Path
from typing import (
Any, Iterator, List, Mapping, MutableMapping, Optional, Union
)
from pylspci.device import Device
from pylspci.fields import PCIAccessParameter
from pylspci.filters import SlotFilter, DeviceFilter
from pylspci.filters import DeviceFilter, SlotFilter
from pylspci.parsers.base import Parser
import subprocess
OptionalPath = Optional[Union[str, Path]]
@ -207,7 +209,7 @@ class CommandBuilder(object):
_params: MutableMapping[str, Any] = {}
_parser: Optional[Parser] = None
def __init__(self, **kwargs: Mapping[str, Any]):
def __init__(self, **kwargs: Any):
self._params = kwargs
def __iter__(self) -> Iterator[Union[str, Device, PCIAccessParameter]]:

View File

@ -1,5 +1,15 @@
from typing import NamedTuple, Optional, List
from pylspci.fields import Slot, NameWithID
from typing import Dict, List, NamedTuple, Optional, Union
from pylspci.fields import NameWithID, NameWithIDDict, Slot, SlotDict
DeviceDict = Dict[str, Union[
int,
str,
SlotDict,
NameWithIDDict,
List[str],
None,
]]
class Device(NamedTuple):
@ -10,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[int] = None
physical_slot: Optional[str] = None
"""
The device's physical slot number (Linux only).
:type: int 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,
}

View File

@ -1,7 +1,11 @@
from functools import partial
from typing import Optional, Any
import re
from functools import partial
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)
@ -18,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:
@ -76,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):
"""
@ -86,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})\]$')
@ -132,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):
"""
@ -143,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(
@ -184,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,
}

View File

@ -1,7 +1,8 @@
from abc import ABC, abstractmethod
from typing import Optional, Pattern, ClassVar, Dict, Type, TypeVar, Any
from pylspci.fields import hexstring
import re
from abc import ABC, abstractmethod
from typing import Any, ClassVar, Dict, Optional, Pattern, Type, TypeVar
from pylspci.fields import hexstring
T = TypeVar('T', bound='Filter')
@ -42,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]
@ -115,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]

View File

@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from typing import Union, Mapping, Iterable, List, Dict, Any
from typing import Any, Dict, Iterable, List, Union
from pylspci.device import Device
@ -30,7 +31,7 @@ class Parser(ABC):
:rtype: List[Device]
"""
def run(self, **kwargs: Mapping[str, Any]) -> List[Device]:
def run(self, **kwargs: Any) -> List[Device]:
"""
Run the lspci command with the given arguments, defaulting to the
parser's default arguments, and parse the result.
@ -38,7 +39,7 @@ class Parser(ABC):
:param \\**kwargs: Optional arguments to override the parser's default
arguments. See :func:`lspci`'s documentation for a list of
available arguments.
:type \\**kwargs: Mapping[str, Any]
:type \\**kwargs: Any
:returns: A list of parsed devices.
:rtype: List[Device]
"""

View File

@ -1,10 +1,12 @@
from typing import Union, List, Iterable
from cached_property import cached_property
from pylspci.parsers.base import Parser
from pylspci.fields import hexstring, Slot, NameWithID
from pylspci.device import Device
import argparse
import shlex
from typing import Any, Iterable, List, Union
from cached_property import cached_property
from pylspci.device import Device
from pylspci.fields import NameWithID, Slot, hexstring
from pylspci.parsers.base import Parser
class SimpleParser(Parser):
@ -86,3 +88,11 @@ class SimpleParser(Parser):
if isinstance(args, str):
args = shlex.split(args)
return Device(**vars(self._parser.parse_args(args)))
def run(self, **kwargs: Any) -> List[Device]:
if kwargs.get('verbose'):
raise ValueError(
'Verbose output is unsupported from the SimpleParser. '
'Please use the pylspci.parsers.VerboseParser instead.'
)
return super().run(**kwargs)

View File

@ -1,13 +1,14 @@
from typing import Union, List, Dict, Iterable, NamedTuple, Callable, Any
from pylspci.parsers.base import Parser
from pylspci.device import Device
from pylspci.fields import hexstring, Slot, NameWithID
import warnings
from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Union
from pylspci.device import Device
from pylspci.fields import NameWithID, Slot, hexstring
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'
)
@ -20,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
"""
@ -74,7 +69,7 @@ class VerboseParser(Parser):
),
'NUMANode': FieldMapping(field_name='numa_node', field_type=int),
'IOMMUGroup': FieldMapping(field_name='iommu_group', field_type=int),
'PhySlot': FieldMapping(field_name='physical_slot', field_type=int),
'PhySlot': FieldMapping(field_name='physical_slot', field_type=str),
}
def _parse_device(self, device_data: Union[str, Iterable[str]]) -> Device:

View File

@ -1,6 +1,7 @@
from unittest import TestCase
from unittest.mock import patch, call, MagicMock
from pathlib import Path
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from pylspci.command import CommandBuilder, IDResolveOption
from pylspci.parsers import SimpleParser, VerboseParser

View File

@ -1,8 +1,10 @@
from unittest import TestCase
from unittest.mock import patch, call, MagicMock
from unittest.mock import MagicMock, call, patch
from pylspci.command import (
IDResolveOption, list_access_methods, list_pcilib_params, lspci
)
from pylspci.fields import PCIAccessParameter
from pylspci.command import \
lspci, list_access_methods, list_pcilib_params, IDResolveOption
class TestCommand(TestCase):

View File

@ -1,6 +1,7 @@
from unittest import TestCase
from unittest.mock import patch, call, MagicMock
from typing import List
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from pylspci.device import Device
from pylspci.parsers import SimpleParser
@ -98,3 +99,12 @@ class TestSimpleParser(TestCase):
self.assertEqual(cmd_mock.call_count, 1)
self.assertEqual(cmd_mock.call_args, call())
def test_verbose_error(self) -> None:
with self.assertRaises(ValueError) as ctx:
self.parser.run(verbose=True)
self.assertEqual(
ctx.exception.args[0],
'Verbose output is unsupported from the SimpleParser. '
'Please use the pylspci.parsers.VerboseParser instead.'
)

View File

@ -1,6 +1,7 @@
from unittest import TestCase
from unittest.mock import patch, call, MagicMock
from typing import List
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from pylspci.device import Device
from pylspci.parsers import VerboseParser
@ -55,7 +56,7 @@ class TestVerboseParser(TestCase):
self.assertListEqual(dev.kernel_modules, ['nouveau', 'nvidia'])
self.assertEqual(dev.numa_node, 0)
self.assertEqual(dev.iommu_group, 1)
self.assertEqual(dev.physical_slot, 4)
self.assertEqual(dev.physical_slot, '4')
def test_parse_str(self) -> None:
devices: List[Device] = self.parser.parse(SAMPLE_DEVICE)
@ -86,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')

View File

@ -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',
})

View File

@ -1,5 +1,6 @@
from unittest import TestCase
from pylspci.fields import Slot, NameWithID, PCIAccessParameter
from pylspci.fields import NameWithID, PCIAccessParameter, Slot
class TestSlot(TestCase):
@ -72,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):
@ -127,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):
@ -158,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",
})

View File

@ -1,5 +1,6 @@
from unittest import TestCase
from pylspci.filters import SlotFilter, DeviceFilter
from pylspci.filters import DeviceFilter, SlotFilter
class TestSlotFilter(TestCase):

View File

@ -1,6 +1,4 @@
flake8>=3.5
doc8>=0.8.0
Sphinx>=1.8.1
coverage>=4.5
codecov>=2.0
mypy>=0.720
pre-commit>=2.9.2

View File

@ -1 +1 @@
cached-property>=1.5.1
cached-property>=1.5.1

View File

@ -10,3 +10,6 @@ disallow_incomplete_defs=True
disallow_untyped_defs=True
check_untyped_defs=True
no_implicit_optional=True
[isort]
multi_line_output=5

View File

@ -1,7 +1,8 @@
#!/usr/bin/env python3
from setuptools import setup, find_packages
from typing import List
from setuptools import find_packages, setup
def read_requirements(filename: str) -> List[str]:
return [req.strip() for req in open(filename)]
@ -21,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)",
@ -44,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",
}
)