Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate code linting to Ruff, apply fixes #15

Merged
merged 1 commit into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 3 additions & 59 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,6 @@ repos:
- id: mixed-line-ending
args: ['--fix=auto'] # replace 'auto' with 'lf' to enforce Linux/Mac line endings or 'crlf' for Windows

## If you want to avoid flake8 errors due to unused vars or imports:
# - repo: https://github.com/myint/autoflake
# rev: v1.4
# hooks:
# - id: autoflake
# args: [
# --in-place,
# --remove-all-unused-imports,
# --remove-unused-variables,
# ]

- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort

- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
Expand All @@ -46,44 +30,10 @@ repos:
# - id: blacken-docs
# additional_dependencies: [black]

- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.261
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
- pep8-naming
- flake8-length
- tryceratops
- flake8-return
- flake8-newspaper-style
- flake8-warnings
- flake8-encodings
- flake8-simplify
- flake8-pie
- flake8-comprehensions
- flake8-picky-parentheses
# Documentation
- flake8-docstrings
- flake8-rst
- flake8-rst-docstrings
# Typing
- flake8-future-annotations
- flake8-annotations
- flake-type-annotations-plugin
# Tests
- flake8-pytest-style

- repo: https://github.com/pycqa/pylint
rev: v2.16.0b1
hooks:
- id: pylint
args:
[
--disable=duplicate-code,
--disable=logging-fstring-interpolation,
src,
]
- id: ruff
additional_dependencies:
- bleak
- pytest
Expand All @@ -95,12 +45,6 @@ repos:
- id: interrogate
args: [--verbose, --omit-covered-files, --fail-under=100]

- repo: https://github.com/asottile/pyupgrade
rev: "v3.3.1"
hooks:
- id: pyupgrade
args: ["--py37-plus"]

- repo: https://github.com/codespell-project/codespell
rev: v2.2.2
hooks:
Expand Down
26 changes: 26 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,29 @@ build-backend = "setuptools.build_meta"
# For smarter version schemes and other configuration options,
# check out https://github.com/pypa/setuptools_scm
version_scheme = "no-guess-dev"

[tool.ruff]
select = ["ALL"]
ignore = [
"ANN101", # flake8-annotations
"ANN102",
"ARG002", # flake8-unused-arguments
"ARG003",
"DTZ001", # flake8-datetimez
"DTZ006",
"FBT", # flake8-boolean-trap
"T20", # flake8-print
]
# Always generate Python 3.7-compatible code
target-version = "py37"

src = ["src", "test"]

[tool.ruff.per-file-ignores]
"tests/*" = [
"INP001", # implicit namespace
"S101", # assert
]

[tool.ruff.pydocstyle]
convention = "google"
40 changes: 0 additions & 40 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -114,46 +114,6 @@ testpaths = tests
no_vcs = 1
formats = bdist_wheel

[flake8]
# Some sane defaults for the code style checker flake8
max_line_length = 88
# flake8-docstrings config
docstring-convention = google
# For flake8-rst-docstrings:
rst-roles =
class,
func,
ref,
meth,
mod,
obj,
rst-directives =
envvar,
exception,
rst-substitutions =
version,
extend_ignore = E203, W503
# ^ Black-compatible
# E203 and W503 have edge cases handled by black
RST307,
# Google Python style is not RST until after processed by Napoleon
# See https://github.com/peterjc/flake8-rst-docstrings/issues/17
RST201,RST203,RST301,
# Missing type annotations for self and cls in (class) method
ANN101,ANN102,
# Handled by flake8-length
E501, W505,
# Opinionated check by flake8-picky-parentheses
PAR101,
exclude =
.tox
build
dist
.eggs
docs/conf.py
# flake8-pytest-style config
pytest-parametrize-names-type = csv

[pyscaffold]
# PyScaffold's parameters when the project was created.
# This will be used when updating. Do not change!
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
if __name__ == "__main__":
try:
setup(use_scm_version={"version_scheme": "no-guess-dev"})
except: # noqa
except:
print(
"\n\nAn error occurred while building the project, "
"please ensure you have the most updated version of setuptools, "
"setuptools_scm and wheel with:\n"
" pip install -U setuptools setuptools_scm wheel\n\n"
" pip install -U setuptools setuptools_scm wheel\n\n",
)
raise
52 changes: 29 additions & 23 deletions src/bluetooth_clocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@
from pathlib import Path
from pkgutil import iter_modules
from time import time
from typing import ClassVar, Type # noqa: F401
from uuid import UUID
from typing import TYPE_CHECKING, ClassVar

if TYPE_CHECKING:
from uuid import UUID

from bleak import BleakClient
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData

if TYPE_CHECKING:
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData

from bluetooth_clocks.exceptions import TimeNotReadableError, UnsupportedDeviceError

Expand All @@ -29,7 +33,7 @@

try:
# Change here if project is renamed and does not equal the package name
dist_name = "bluetooth-clocks" # pylint: disable=invalid-name
dist_name = "bluetooth-clocks"
__version__ = version(dist_name)
except PackageNotFoundError: # pragma: no cover
__version__ = "unknown"
Expand Down Expand Up @@ -83,33 +87,33 @@ class BluetoothClock(ABC):
have a name.
"""

DEVICE_TYPE: ClassVar[str] # noqa: CCE001
DEVICE_TYPE: ClassVar[str]
"""The name of the device type."""

SERVICE_UUID: ClassVar[UUID] # noqa: CCE001
SERVICE_UUID: ClassVar[UUID]
"""The UUID of the service used to read/write the time."""

CHAR_UUID: ClassVar[UUID] # noqa: CCE001
CHAR_UUID: ClassVar[UUID]
"""The UUID of the characteristic used to read/write the time."""

TIME_GET_FORMAT: ClassVar[str | None] # noqa: CCE001
TIME_GET_FORMAT: ClassVar[str | None]
"""The format string to convert bytes read from the device to a time.
This is ``None`` if the device doesn't support reading the time.
"""

TIME_SET_FORMAT: ClassVar[str] # noqa: CCE001
TIME_SET_FORMAT: ClassVar[str]
"""The format string to convert a time to bytes written to the device."""

WRITE_WITH_RESPONSE: ClassVar[bool] # noqa: CCE001
WRITE_WITH_RESPONSE: ClassVar[bool]
"""``True`` if the bytes to set the time should use write with response."""

LOCAL_NAME: ClassVar[str | None] # noqa: CCE001
LOCAL_NAME: ClassVar[str | None]
"""The local name used to recognize this type of device.
This is ``None`` if the local name isn't used to recognize the device."""

LOCAL_NAME_STARTS_WITH: ClassVar[bool | None] # noqa: CCE001
LOCAL_NAME_STARTS_WITH: ClassVar[bool | None]
"""Whether the local name should start with `LOCAL_NAME`.
``True`` if the start of `LOCAL_NAME` is used to recognize this type of device.
Expand All @@ -128,7 +132,9 @@ def __init__(self, device: BLEDevice) -> None:

@classmethod
def create_from_advertisement(
cls, device: BLEDevice, advertisement_data: AdvertisementData
cls,
device: BLEDevice,
advertisement_data: AdvertisementData,
) -> BluetoothClock:
"""Create object of a :class:`BluetoothClock` subclass from advertisement data.
Expand Down Expand Up @@ -175,7 +181,7 @@ def is_readable(cls) -> bool:
@classmethod
def recognize(
cls,
device: BLEDevice, # pylint: disable=unused-argument
device: BLEDevice,
advertisement_data: AdvertisementData,
) -> bool:
"""Recognize this device type from advertisement data.
Expand Down Expand Up @@ -246,9 +252,7 @@ def recognize_from_local_name(
return local_name.startswith(cls.LOCAL_NAME)
return local_name == cls.LOCAL_NAME

def get_time_from_bytes( # pylint: disable=unused-argument
self, time_bytes: bytes
) -> float:
def get_time_from_bytes(self, time_bytes: bytes) -> float:
"""Convert bytes read from a device to a timestamp.
Override this method in a subclass for a device that supports getting the time.
Expand All @@ -267,12 +271,12 @@ def get_time_from_bytes( # pylint: disable=unused-argument
>>> from bluetooth_clocks.devices.xiaomi import LYWSD02
>>> from bleak.backends.device import BLEDevice
>>> from datetime import datetime
>>> clock = LYWSD02(BLEDevice("E7:2E:00:B1:38:96"))
>>> clock = LYWSD02(BLEDevice("E7:2E:00:B1:38:96", "", {}, -67))
>>> timestamp = clock.get_time_from_bytes(
... bytes([0xdd, 0xbc, 0xb9, 0x63, 0x00]))
>>> print(datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S"))
2023-01-07 18:41:33
"""
""" # noqa: E501
raise TimeNotReadableError

@abstractmethod
Expand All @@ -294,7 +298,7 @@ def get_bytes_from_time(self, timestamp: float, ampm: bool = False) -> bytes:
>>> from bluetooth_clocks.devices.thermopro import TP393
>>> from bleak.backends.device import BLEDevice
>>> from datetime import datetime
>>> clock = TP393(BLEDevice("10:76:36:14:2A:3D"))
>>> clock = TP393(BLEDevice("10:76:36:14:2A:3D", "TP393 (2A3D)", {}, -67))
>>> timestamp = datetime.fromisoformat("2023-01-07 17:32:50").timestamp()
>>> clock.get_bytes_from_time(timestamp, ampm=True).hex()
'a517010711203206005a'
Expand All @@ -321,7 +325,9 @@ async def get_time(self) -> float:
return self.get_time_from_bytes(time_bytes)

async def set_time(
self, timestamp: float | None = None, ampm: bool = False
self,
timestamp: float | None = None,
ampm: bool = False,
) -> None:
"""Set the time of the Bluetooth clock.
Expand All @@ -347,7 +353,7 @@ async def set_time(

# Iterate through the modules in the module `device`.
package_dir = Path(__file__).resolve().parent / "devices"
for _, module_name, _ in iter_modules([str(package_dir)]): # type: ignore
for _, module_name, _ in iter_modules([str(package_dir)]): # type: ignore[assignment]
# Import the module and iterate through its attributes
module = import_module(f"{__name__}.devices.{module_name}")
for attribute_name in dir(module):
Expand Down
13 changes: 9 additions & 4 deletions src/bluetooth_clocks/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ def parse_args(args: list[str]) -> Namespace:

# Parser for the "discover" subcommand
parser_discover = subparsers.add_parser(
"discover", help="discover supported Bluetooth clocks"
"discover",
help="discover supported Bluetooth clocks",
)
parser_discover.add_argument(
"-s",
Expand All @@ -92,7 +93,8 @@ def parse_args(args: list[str]) -> Namespace:

# Parser for the "get" subcommand
parser_get = subparsers.add_parser(
"get", help="get the time from a Bluetooth clock"
"get",
help="get the time from a Bluetooth clock",
)
parser_get.add_argument(
"-a",
Expand Down Expand Up @@ -128,7 +130,7 @@ def parse_args(args: list[str]) -> Namespace:
"-t",
"--time",
type=str,
help="the time to set, in ISO 8601 format (e.g. 2023-01-10T16:20, default: current time)",
help="the time to set, in ISO 8601 format (e.g. 2023-01-10T16:20, default: current time)", # noqa: E501
)
parser_set.add_argument(
"-p",
Expand All @@ -150,7 +152,10 @@ def setup_logging(loglevel: int) -> None:
"""
logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
logging.basicConfig(
level=loglevel, stream=sys.stdout, format=logformat, datefmt="%Y-%m-%d %H:%M:%S"
level=loglevel,
stream=sys.stdout,
format=logformat,
datefmt="%Y-%m-%d %H:%M:%S",
)


Expand Down
Loading