Skip to content

Commit

Permalink
Merge pull request #21 from claui/api-docs
Browse files Browse the repository at this point in the history
Improve API documentation
  • Loading branch information
claui authored Dec 8, 2024
2 parents f4da1dd + 945d732 commit bd59169
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/debian/itchcraft.*.debhelper
/debian/itchcraft.substvars
/dist/
/doc/sphinx/autoapi/
/doc/sphinx/api/
/.pc/
/.venv/
*.egg-info
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ See
or `man itchcraft` for details.

You can also browse the
[API reference on Read the Docs](https://itchcraft.readthedocs.io/en/stable/autoapi/itchcraft/)
[API reference on Read the Docs](https://itchcraft.readthedocs.io/en/stable/api/itchcraft/)
for details.

## Contributing to Itchcraft
Expand Down
38 changes: 27 additions & 11 deletions doc/sphinx/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
# type: ignore

project = 'Itchcraft'
copyright = '2024 Claudia Pellegrino'
executable = 'itchcraft'
author = 'Claudia Pellegrino <[email protected]>'
description = 'Tech demo for interfacing with heat-based USB insect bite healers'
description = (
'Tech demo for interfacing with heat-based USB insect bite healers'
)

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand All @@ -33,26 +36,38 @@
'special-members',
'imported-members',
]
autoapi_root = 'api'
autoapi_type = 'python'
autodoc_typehints = 'description'

html_theme = 'sphinx_rtd_theme'

python_display_short_literal_types = True
python_use_unqualified_type_names = True

myst_enable_extensions = [
'deflist',
]


def skip_module(app, what, name, obj, skip, options):
if what != 'module':
return skip
if name in [
'itchcraft.__main__',
'itchcraft.cli',
'itchcraft.fire_workarounds',
'itchcraft.version',
'itchcraft.settings',
]:
return True
if what == 'data':
return skip or name.endswith('.logger')
if what == 'method':
return skip or name.endswith('.__str__')
if what == 'module':
return (
skip
or name
in [
'itchcraft.__main__',
'itchcraft.cli',
'itchcraft.fire_workarounds',
'itchcraft.version',
'itchcraft.settings',
]
or obj.obj['relative_path'].startswith('itchcraft/stubs')
)
return skip


Expand All @@ -67,6 +82,7 @@ def setup(sphinx):
'**/itchcraft/fire_workarounds/**',
'**/itchcraft/version/**',
'**/itchcraft/settings/**',
'**/itchcraft/stubs/**',
]

# Man page output
Expand Down
2 changes: 1 addition & 1 deletion doc/sphinx/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Documentation for Itchcraft
:caption: Contents

usage
autoapi/index
api/index

.. include:: ../../README.md
:parser: myst_parser.sphinx_
2 changes: 1 addition & 1 deletion itchcraft/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

# pylint: disable=too-few-public-methods
class Api:
"""Tech demo for interfacing with heat-based USB insect bite healers"""
"""Tech demo for interfacing with heat-based USB insect bite healers."""

# pylint: disable=no-self-use
def info(self) -> None:
Expand Down
6 changes: 5 additions & 1 deletion itchcraft/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ def serial_number(self) -> Optional[str]:


class UsbBulkTransferDevice(BulkTransferDevice):
"""USB device with two bulk transfer endpoints."""
"""USB device with two bulk transfer endpoints.
:param device:
the PyUSB device with which to initiate the bulk transfer.
"""

MAX_RESPONSE_LENGTH = 12

Expand Down
14 changes: 13 additions & 1 deletion itchcraft/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,19 @@ def from_usb_device(
usb_device: usb.core.Device,
support_statement: SupportStatement,
) -> BiteHealerMetadata:
"""Creates a metadata object from a USB device."""
"""Creates a metadata object from a USB device.
:param usb_device:
the PyUSB device to be queried for metadata.
:param support_statement:
Describes the level of support that Itchcraft offers for
`usb_device`.
:return:
a metadata object that unifies info from both the PyUSB device
and `support_statement`.
"""

def try_get_usb_attribute(
name: Literal['product', 'serial_number'],
Expand Down
2 changes: 1 addition & 1 deletion itchcraft/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class CliError(Exception):
"""An user-facing error message."""
"""A user-facing error message."""


class BackendInitializationError(Exception):
Expand Down
15 changes: 13 additions & 2 deletions itchcraft/heat_it.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@


class HeatItDevice(BiteHealer):
"""A “heat it” bite healer, configured over USB."""
"""A “heat it” bite healer, configured over USB.
:param device:
the backend object to which to delegate the USB bulk transfer.
"""

device: BulkTransferDevice

Expand All @@ -43,6 +47,9 @@ def get_status(self) -> bytes:
def msg_start_heating(self, preferences: Preferences) -> bytes:
"""Issues a `MSG_START_HEATING` command and returns the
response.
:param preferences:
how the user wants the device to be configured.
"""

def duration_code() -> int:
Expand Down Expand Up @@ -94,7 +101,11 @@ def self_test(self) -> None:
logger.debug('Response: %s', self.get_status().hex(' '))

def start_with_preferences(self, preferences: Preferences) -> None:
"""Tells the device to start heating up."""
"""Tells the device to start heating up.
:param preferences:
how the user wants the device to be configured.
"""
logger.debug(
'Response: %s', self.msg_start_heating(preferences).hex(' ')
)
Expand Down
71 changes: 56 additions & 15 deletions itchcraft/prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,46 @@

from .errors import CliError

E = TypeVar('E', bound=Enum)
_E = TypeVar('_E', bound=Enum)

# If a CLI switch is backed by an enum, then allow the enum to stand in
# for that switch
CliEnum = Union[str, E]
CliEnum = Union[str, _E]
"""Helper union type consisting of an :py:class:`~enum.Enum` and its stringified values.
If a command line switch is backed by an `Enum`, then allow the enum
to stand in for that switch.
For example, if you have the following `Enum` class:
.. code:: python
from enum import Enum
class Widget(Enum):
FOO = 1
BAR = 2
and an associated command line switch ``--widget``, which can take the
form of either ``--widget foo`` and ``--widget bar``, then
`CliEnum[Widget]` means that the four values ``Widget.FOO``,
``Widget.BAR``, ``"foo"``, and ``"bar"`` should be accepted.
`Widget.FOO` is equivalent to ``"foo"``, while `Widget.BAR` stands in
for ``"bar"``:
.. code:: python
from itchcraft.prefs import CliEnum, parse
def frob(widget: CliEnum[Widget]) -> None:
real_widget: Widget = parse(widget, Widget)
...
frob("foo")
frob("bar")
frob(Widget.FOO) # same as frob("foo")
frob(Widget.BAR) # same as frob("bar")
"""


class SkinSensitivity(Enum):
Expand Down Expand Up @@ -66,28 +101,34 @@ def __str__(self) -> str:
)


def default(enum_type: type[E]) -> str:
"""Returns the default preference for a given Enum type.
def default(enum_type: type[_E]) -> str:
"""Returns the default preference for a given :py:class:`~enum.Enum` type.
:param enum_type:
Enum type which exists as an attribute in Preferences and
whose corresponding attribute name is equal to the type name
converted to snake case.
Enum type which exists as an attribute in
:py:class:`.Preferences` and whose corresponding attribute name
is equal to the type name converted to snake case.
"""
default_value: E = getattr(Preferences, _snake_case_name(enum_type))
default_value: _E = getattr(
Preferences, _snake_case_name(enum_type)
)
return default_value.name.lower()


# pylint: disable=raise-missing-from
def parse(value: CliEnum[E], enum_type: type[E]) -> E:
"""Parses a given value into an Enum if it isn’t one yet.
Returns the value itself if it’s already an Enum.
def parse(value: CliEnum[_E], enum_type: type[_E]) -> _E:
"""Parses a given value into an :py:class:`~enum.Enum` if it isn’t one yet.
:param value:
an Enum value or a corresponding name, written in lower case.
an `Enum` value or a corresponding name, written in lower case.
:param enum_type:
the type of the Enum to parse into.
the type of the `Enum` to parse into.
:return:
an instance of `enum_type` that represents `value`.
If `value` is already an instance of `enum_type`, then the
return value is `value` itself.
"""
if isinstance(value, enum_type):
return value
Expand Down
5 changes: 2 additions & 3 deletions itchcraft/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@


def start_with_preferences(preferences: Preferences) -> None:
"""Activates (i.e. heats up) a connected USB bite healer for
demonstration purposes.
"""Activates (i.e. heats up) a connected USB bite healer for demonstration purposes.
:param preferences:
User preferences for the settings of the bite healer.
how the user wants the device to be configured.
"""
logger.warning('This app is only a tech demo')
logger.warning('and NOT for medical use.')
Expand Down
31 changes: 22 additions & 9 deletions itchcraft/support.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Declaring supported bite healers"""
"""Database of supported bite healers."""

from collections.abc import Iterator
from contextlib import AbstractContextManager, contextmanager
Expand All @@ -14,33 +14,45 @@

@dataclass(frozen=True)
class SupportStatement:
"""Statement that establishes whether or not a given combination
"""Describes the level of support that Itchcraft offers for a given device.
A metadata object that establishes whether or not a given combination
of vendor ID (VID) and product id (PID) is a supported bite healer,
and which model it is."""

vid: int
"""USB vendor ID"""
"""The USB vendor ID."""
pid: int
"""USB product ID"""
"""The USB product ID."""
vendor_name: str
"""Canonical vendor name from Itchcraft’s point of view"""
"""The canonical vendor name from Itchcraft’s point of view."""
product_name: str
"""Canonical product name from Itchcraft’s point of view"""
"""The canonical product name from Itchcraft’s point of view."""
supported: bool = True
"""Whether or not Itchcraft supports this model"""
"""Whether or not Itchcraft supports this model."""
comment: Optional[str] = None
"""Additional comments on the support status of this model"""
"""Additional comments on the support status of this model."""
connection_supplier: Optional[
Callable[[usb.core.Device], AbstractContextManager[BiteHealer]]
] = None
"""Callable that establishes a connection to this model"""
"""An optional :py:class:`~typing.Callable` that, when invoked,
establishes a connection to an attached device of this model.
:param usb_device:
a PyUSB device to which to connect.
:return:
a context manager representing a :py:class:`~.types.BiteHealer`.
"""


class VidPid(NamedTuple):
"""Tuple of USB vendor ID (VID) and product ID (PID)."""

vid: int
"""The USB vendor ID."""
pid: int
"""The USB product ID."""

def __str__(self) -> str:
return (
Expand Down Expand Up @@ -174,3 +186,4 @@ def _heat_it_device(
""",
),
]
"""Hard-coded database of support statements for various bite healer models."""
8 changes: 7 additions & 1 deletion itchcraft/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@


SizedPayload = Union[bytes, Collection[int]]
"""Helper union type consisting of :py:class:`bytes` and :py:class:`collections.abc.Collection[int]`.
""" # pylint: disable=line-too-long


class BiteHealer(ABC):
Expand All @@ -20,4 +22,8 @@ def self_test(self) -> None:

@abstractmethod
def start_with_preferences(self, preferences: Preferences) -> None:
"""Tells the device to start heating up."""
"""Tells the device to start heating up.
:param preferences:
how the user wants the device to be configured.
"""

0 comments on commit bd59169

Please sign in to comment.