Skip to content

Commit

Permalink
ci: introduce ruff as linter (#381)
Browse files Browse the repository at this point in the history
This PR introduces to use ruff as python linter in the project, like pre-commit.
It replaces the use of isort, flake8 and related plugins and only use ruff as the only tools to lint the code, while more plugins are introduced, see pyproject.ruff.lint.select for all selected rules set.
As the first time of introducing this tool, a whole fixes to the detected issues are also introduced in this PR. No changes of features or behaviors are introduced.
  • Loading branch information
Bodong-Yang authored Aug 26, 2024
1 parent 6fd6d28 commit 8f5d936
Show file tree
Hide file tree
Showing 22 changed files with 98 additions and 97 deletions.
14 changes: 0 additions & 14 deletions .flake8

This file was deleted.

23 changes: 5 additions & 18 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ repos:
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.2
hooks:
- id: ruff
args: [--fix]
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2
Expand All @@ -18,24 +23,6 @@ repos:
# pre-commit's default_language_version, see
# https://pre-commit.com/#top_level-default_language_version
language_version: python3.11
# - repo: https://github.com/pycqa/isort
# rev: 5.13.2
# hooks:
# - id: isort
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear==24.2.6
- flake8-comprehensions
- flake8-simplify
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "2.2.1"
hooks:
- id: pyproject-fmt
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
additional_dependencies: ["tox>=4.9"]
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.41.0
hooks:
Expand Down
64 changes: 46 additions & 18 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,11 @@ dependencies = [
optional-dependencies.dev = [
"black",
"coverage",
"flake8",
"isort",
"pytest==7.1.2",
"pytest-asyncio==0.23.8",
"pytest-mock==3.14",
"requests-mock",
"ruff",
]
urls.Source = "https://github.com/tier4/ota-client"

Expand Down Expand Up @@ -85,22 +84,6 @@ features = [
"dev",
]

[tool.black]
line-length = 88
target-version = [
'py38',
]
extend-exclude = '''(
^.*(_pb2.pyi?|_pb2_grpc.pyi?)$
)'''

[tool.isort]
profile = "black"
extend_skip_glob = [
"*_pb2.py*",
"_pb2_grpc.py*",
]

[tool.pytest.ini_options]
asyncio_mode = "auto"
log_auto_indent = true
Expand All @@ -111,6 +94,18 @@ testpaths = [
"./tests",
]

[tool.black]
line-length = 88
target-version = [
'py38',
'py39',
'py310',
'py311',
]
extend-exclude = '''(
^.*(_pb2.pyi?|_pb2_grpc.pyi?)$
)'''

[tool.coverage.run]
branch = false
relative_files = true
Expand Down Expand Up @@ -142,7 +137,40 @@ exclude = [
"**/__pycache__",
]
ignore = [
"proto/**",
"**/*_pb2.py*",
"**/*_pb2_grpc.py*",
]
pythonVersion = "3.8"

[tool.ruff]
target-version = "py38"
# NOTE: not include tests and tools for now
include = [
"tests/**/*.py",
"src/**/*.py",
"pyproject.toml",
]
extend-exclude = [
"*_pb2.py*",
"*_pb2_grpc.py*",
]

[tool.ruff.lint]
select = [
"E4",
"E7",
"E9",
"F", # pyflakes
"Q", # flake8-quotes
"I", # isort
"B", # flake8-bugbear
"A", # flake8-builtins
"ICN", # flake8-import-conventions
]
ignore = [
"E266", # (too many leading '#'): sometimes we use multiple # for separting sections
"E203", # (white space before ':'): this error conflicts with black linting
"E701", # (multiple statements on one line)
"S101", # (use of assert): mostly we use assert for typing
]
4 changes: 2 additions & 2 deletions src/ota_metadata/legacy/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def __set__(self, obj, value: Any) -> None:
except KeyError:
raise ValueError(
f"invalid metafile field {self.field_name}: {value}"
)
) from None
# normal key-value field
else:
try:
Expand All @@ -208,7 +208,7 @@ def __set__(self, obj, value: Any) -> None:
except KeyError:
raise ValueError(
f"invalid metafile field {self.field_name}: {value}"
)
) from None
raise ValueError(f"attempt to assign invalid {value=} to {self.field_name=}")

def __set_name__(self, owner: type, name: str):
Expand Down
3 changes: 1 addition & 2 deletions src/otaclient/app/ota_client_stub.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@

from typing_extensions import Self

from ota_proxy import OTAProxyContextProto
from ota_proxy import OTAProxyContextProto, subprocess_otaproxy_launcher
from ota_proxy import config as local_otaproxy_cfg
from ota_proxy import subprocess_otaproxy_launcher
from otaclient import log_setting
from otaclient.boot_control._common import CMDHelperFuncs
from otaclient.configs.ecu_info import ECUContact
Expand Down
4 changes: 3 additions & 1 deletion src/otaclient/boot_control/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,9 @@ def preserve_ota_folder_to_standby(self):
_dst = self.standby_slot_mount_point / Path(cfg.OTA_DIR).relative_to("/")
shutil.copytree(_src, _dst, dirs_exist_ok=True)
except Exception as e:
raise ValueError(f"failed to copy /boot/ota from active to standby: {e!r}")
raise ValueError(
f"failed to copy /boot/ota from active to standby: {e!r}"
) from e

def prepare_standby_dev(
self,
Expand Down
2 changes: 1 addition & 1 deletion src/otaclient/boot_control/_grub.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def grub_mkconfig() -> str:
except CalledProcessError as e:
raise ValueError(
f"grub-mkconfig failed: {e.returncode=}, {e.stderr=}, {e.stdout=}"
)
) from None

@staticmethod
def grub_reboot(idx: int):
Expand Down
8 changes: 5 additions & 3 deletions src/otaclient/boot_control/_jetson_cboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ def is_unified_enabled(cls) -> bool | None:
return False
elif e.returncode == 69:
return
raise ValueError(f"{cmd} returns unexpected result: {e.returncode=}, {e!r}")
raise ValueError(
f"{cmd} returns unexpected result: {e.returncode=}, {e!r}"
) from None


class NVUpdateEngine:
Expand Down Expand Up @@ -189,7 +191,7 @@ def __init__(self):
except Exception as e:
_err_msg = f"failed to detect BSP version: {e!r}"
logger.error(_err_msg)
raise JetsonCBootContrlError(_err_msg)
raise JetsonCBootContrlError(_err_msg) from None
logger.info(f"{bsp_version=}")

# ------ sanity check, jetson-cboot is not used after BSP R34 ------ #
Expand Down Expand Up @@ -218,7 +220,7 @@ def __init__(self):
except subprocess.CalledProcessError:
_err_msg = "rootfs A/B is not enabled!"
logger.error(_err_msg)
raise JetsonCBootContrlError(_err_msg)
raise JetsonCBootContrlError(_err_msg) from None
self.unified_ab_enabled = unified_ab_enabled

if unified_ab_enabled:
Expand Down
8 changes: 4 additions & 4 deletions src/otaclient/boot_control/_rpi_boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def __init__(self) -> None:
except Exception as e:
_err_msg = f"failed to detect partition layout: {e!r}"
logger.error(_err_msg)
raise _RPIBootControllerError(_err_msg)
raise _RPIBootControllerError(_err_msg) from None

# check system-boot partition mount
system_boot_partition = device_tree[1]
Expand Down Expand Up @@ -185,7 +185,7 @@ def __init__(self) -> None:
except ValueError:
raise _RPIBootControllerError(
f"active slot dev not found: {active_slot_dev=}, {rootfs_partitions=}"
)
) from None

if idx == 0: # slot_a
self.active_slot = SLOT_A
Expand Down Expand Up @@ -335,7 +335,7 @@ def update_firmware(self, *, target_slot: SlotID, target_slot_mp: StrOrPath):
except subprocess.CalledProcessError as e:
_err_msg = f"flash-kernel failed: {e!r}\nstderr: {e.stderr.decode()}\nstdout: {e.stdout.decode()}"
logger.error(_err_msg)
raise _RPIBootControllerError(_err_msg)
raise _RPIBootControllerError(_err_msg) from None

try:
# flash-kernel will install the kernel and initrd.img files from /boot to /boot/firmware
Expand All @@ -359,7 +359,7 @@ def update_firmware(self, *, target_slot: SlotID, target_slot_mp: StrOrPath):
except Exception as e:
_err_msg = f"failed to apply new kernel,initrd.img for {target_slot}: {e!r}"
logger.error(_err_msg)
raise _RPIBootControllerError(_err_msg)
raise _RPIBootControllerError(_err_msg) from None

# exposed API methods/properties

Expand Down
6 changes: 3 additions & 3 deletions src/otaclient_api/v2/api_caller.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async def status_call(
return types.StatusResponse.convert(resp)
except Exception as e:
_msg = f"{ecu_id=} failed to respond to status request on-time: {e!r}"
raise ECUNoResponse(_msg)
raise ECUNoResponse(_msg) from e

@staticmethod
async def update_call(
Expand All @@ -63,7 +63,7 @@ async def update_call(
return types.UpdateResponse.convert(resp)
except Exception as e:
_msg = f"{ecu_id=} failed to respond to update request on-time: {e!r}"
raise ECUNoResponse(_msg)
raise ECUNoResponse(_msg) from e

@staticmethod
async def rollback_call(
Expand All @@ -82,4 +82,4 @@ async def rollback_call(
return types.RollbackResponse.convert(resp)
except Exception as e:
_msg = f"{ecu_id=} failed to respond to rollback request on-time: {e!r}"
raise ECUNoResponse(_msg)
raise ECUNoResponse(_msg) from e
2 changes: 1 addition & 1 deletion src/otaclient_common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ def import_from_file(path: Path) -> tuple[str, ModuleType]:
_spec.loader.exec_module(_module) # type: ignore
return _module_name, _module
except Exception:
raise ImportError(f"failed to import module from {path=}.")
raise ImportError(f"failed to import module from {path=}.") from None
8 changes: 4 additions & 4 deletions src/otaclient_common/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ def read_str_from_file(path: Union[Path, str], *, missing_ok=True, default="") -
raise


def write_str_to_file(path: Path, input: str):
path.write_text(input)
def write_str_to_file(path: Path, _input: str):
path.write_text(_input)


def write_str_to_file_sync(path: Union[Path, str], input: str):
def write_str_to_file_sync(path: Union[Path, str], _input: str):
with open(path, "w") as f:
f.write(input)
f.write(_input)
f.flush()
os.fsync(f.fileno())

Expand Down
4 changes: 2 additions & 2 deletions src/otaclient_common/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def iter_chunk(self, src_stream: IO[bytes] | ByteString) -> Iterator[bytes]:
except zstandard.ZstdError as e:
raise BrokenDecompressionError(
f"failure during decompressing file stream: {e!r}"
)
) from e


# ------ OTA-Cache-File-Control protocol implementation ------ #
Expand Down Expand Up @@ -557,7 +557,7 @@ def shutdown(self) -> None:
"""Close all the downloader instances."""
# at final, trigger an update to the total_downloaded_bytes, in case
# we still need the total_downloaded_bytes data after pool shutdown.
self.total_downloaded_bytes
_ = self.total_downloaded_bytes

with self._instance_map_lock:
for _instance in self._instances:
Expand Down
12 changes: 6 additions & 6 deletions src/otaclient_common/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@


def create_swapfile(
swapfile_fpath: str | Path, size_in_MiB: int, *, timeout=900
swapfile_fpath: str | Path, size_in_mibytes: int, *, timeout=900
) -> Path:
"""Create swapfile at <swapfile_fpath> with <size_in_MiB>MiB.
Expand Down Expand Up @@ -62,7 +62,7 @@ def create_swapfile(
"if=/dev/zero",
f"of={str(swapfile_fpath)}",
"bs=1M",
f"count={size_in_MiB}",
f"count={size_in_mibytes}",
],
timeout=timeout,
)
Expand Down Expand Up @@ -106,7 +106,7 @@ def __init__(self, passwd_fpath: str | Path) -> None:
self._by_name[_name] = _uid
self._by_uid = {v: k for k, v in self._by_name.items()}
except Exception as e:
raise ValueError(f"invalid or missing {passwd_fpath=}: {e!r}")
raise ValueError(f"invalid or missing {passwd_fpath=}: {e!r}") from None


class ParsedGroup:
Expand All @@ -131,7 +131,7 @@ def __init__(self, group_fpath: str | Path) -> None:
self._by_name[_raw_list[0]] = int(_raw_list[2])
self._by_gid = {v: k for k, v in self._by_name.items()}
except Exception as e:
raise ValueError(f"invalid or missing {group_fpath=}: {e!r}")
raise ValueError(f"invalid or missing {group_fpath=}: {e!r}") from None


def map_uid_by_pwnam(*, src_db: ParsedPasswd, dst_db: ParsedPasswd, uid: int) -> int:
Expand All @@ -143,7 +143,7 @@ def map_uid_by_pwnam(*, src_db: ParsedPasswd, dst_db: ParsedPasswd, uid: int) ->
try:
return dst_db._by_name[src_db._by_uid[uid]]
except KeyError:
raise ValueError(f"failed to find mapping for {uid}")
raise ValueError(f"failed to find mapping for {uid}") from None


def map_gid_by_grpnam(*, src_db: ParsedGroup, dst_db: ParsedGroup, gid: int) -> int:
Expand All @@ -155,7 +155,7 @@ def map_gid_by_grpnam(*, src_db: ParsedGroup, dst_db: ParsedGroup, gid: int) ->
try:
return dst_db._by_name[src_db._by_gid[gid]]
except KeyError:
raise ValueError(f"failed to find mapping for {gid}")
raise ValueError(f"failed to find mapping for {gid}") from None


#
Expand Down
Loading

1 comment on commit 8f5d936

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/ota_metadata/legacy
   __init__.py110100% 
   parser.py3353888%100, 156, 161, 197–198, 208–209, 212, 224, 282, 292–295, 334–337, 417, 420, 428–430, 443, 452–453, 456–457, 669–670, 673, 700–702, 752, 755–757
   types.py841384%37, 40–42, 112–116, 122–125
src/ota_proxy
   __init__.py361072%59, 61, 63, 72, 81–82, 102, 104–106
   __main__.py770%16–18, 20, 22–23, 25
   _consts.py150100% 
   cache_control_header.py68494%71, 91, 113, 121
   cache_streaming.py1421291%211, 225, 229–230, 265–266, 268, 348, 366–369
   config.py170100% 
   db.py731875%109, 115, 153, 159–160, 163, 169, 171, 192–199, 201–202
   errors.py50100% 
   lru_cache_helper.py47295%84–85
   ota_cache.py2155972%70–71, 140, 151–152, 184–185, 202, 239–243, 247–249, 251, 253–260, 262–264, 267–268, 272–273, 277, 324, 332–334, 413–416, 430, 433–434, 448–449, 451–453, 457–458, 464–465, 496, 502, 529, 581–583
   server_app.py1393971%76, 79, 85, 101, 103, 162, 171, 213–214, 216–218, 221, 226–228, 231–232, 235, 238, 241, 244, 257–258, 261–262, 264, 267, 293–296, 299, 313–315, 321–323
   utils.py140100% 
src/otaclient
   __init__.py5260%17, 19
   __main__.py110%16
   log_setting.py52590%53, 55, 64–66
src/otaclient/app
   __main__.py110%16
   configs.py760100% 
   errors.py1200100% 
   interface.py50100% 
   main.py46589%52–53, 75–77
   ota_client.py37310871%79, 87, 108, 135, 137–138, 140, 144, 148–149, 154–155, 161, 163, 201–204, 210, 214, 220, 339, 351–352, 354, 363, 366, 371–372, 375, 381, 383–387, 406–409, 412–419, 447–450, 496–497, 501, 503–504, 534–535, 544–551, 558, 561–567, 612–615, 623, 659–661, 666–668, 671–672, 674–675, 677, 735–736, 739, 747–748, 751, 762–763, 766, 774–775, 778, 789, 808, 835, 854, 872
   ota_client_stub.py39310972%75–77, 79–80, 88–91, 94–96, 100, 105–106, 108–109, 112, 114–115, 118–120, 123–124, 127–129, 134–139, 143, 146–150, 152–153, 161–163, 166, 203–205, 210, 246, 271, 274, 277, 381, 405, 407, 431, 477, 534, 604–605, 644, 663–665, 671–674, 678–680, 687–689, 692, 696–699, 752, 841–843, 850, 880–881, 884–888, 897–906, 913, 919, 922–923, 927, 930
   update_stats.py104991%57, 103, 105, 114, 116, 125, 127, 148, 179
src/otaclient/boot_control
   __init__.py40100% 
   _common.py24811254%74–75, 96–98, 114–115, 135–136, 155–156, 175–176, 195–196, 218–220, 235–236, 260–266, 287, 295, 313, 321, 340–341, 344–345, 368, 370–379, 381–390, 392–394, 413, 416, 424, 432, 448–450, 452–457, 550, 555, 560, 673, 677–678, 681, 689, 691–692, 718–719, 721–724, 729, 735–736, 739–740, 742, 749–750, 761–767, 777–779, 783–784, 787–788, 791, 797
   _firmware_package.py64395%77, 168, 174
   _grub.py41712869%217, 265–268, 274–278, 315–316, 323–328, 331–337, 340, 343–344, 349, 351–353, 362–368, 370–371, 373–375, 384–386, 388–390, 469–470, 474–475, 527, 533, 559, 581, 585–586, 601–603, 627–630, 642, 646–648, 650–652, 711–714, 739–742, 765–768, 780–781, 784–785, 820, 826, 846–847, 849, 861, 864, 867, 870, 874–876, 894–897, 925–928, 933–941, 946–954
   _jetson_cboot.py27021420%66–67, 74–75, 93–102, 116, 123–124, 136, 142–143, 153–155, 167–168, 179–180, 183–184, 187–188, 191–195, 198–199, 203–204, 209–210, 212–216, 218–224, 226–227, 232, 235, 238–239, 242, 246–247, 251–252, 256, 259, 262, 266–272, 274–276, 281, 284, 287, 291, 298, 300–303, 316, 319, 323, 325–327, 331, 338, 340, 343, 349–350, 355, 363, 371–373, 382–383, 385–387, 393, 396–398, 402–403, 405, 408, 417–419, 422, 425, 428–433, 435–437, 440, 443, 447–452, 456–458, 463–464, 468–469, 472, 475, 478–479, 482, 485, 490, 493, 496–497, 499, 501, 504, 507, 509–510, 513–517, 522–523, 525, 533–537, 539, 542, 545, 556–557, 562, 572, 575–583, 588–596, 601–609, 615–617, 620, 623
   _jetson_common.py1436653%51, 75, 130–135, 137, 142–144, 149–152, 160–161, 168–169, 174–175, 191–192, 194–196, 199–201, 204, 208, 212, 216–218, 224–225, 227, 260, 286–287, 289–291, 295–298, 300–301, 303–307, 309, 316–317, 320, 322, 332, 335–336, 339, 341
   _rpi_boot.py28713453%55, 58, 122–123, 127, 135–138, 152–155, 162–163, 165–166, 171–172, 175–176, 185–186, 224, 230–234, 237, 255–257, 261–263, 268–270, 274–276, 286–287, 290, 293, 295–296, 298–299, 301–303, 309, 312–313, 323–326, 334–338, 340, 342–343, 348–349, 356–362, 393, 395–398, 408–411, 415–416, 418–422, 450–453, 472–475, 480, 483, 501–504, 509–517, 522–530, 547–550, 556–558, 561, 564
   configs.py390100% 
   protocol.py40100% 
   selecter.py382631%45–47, 50–51, 55–56, 59–61, 64, 66, 70, 78–80, 82–83, 85–86, 90, 92–94, 96, 98
src/otaclient/configs
   _common.py80100% 
   ecu_info.py57198%107
   proxy_info.py52296%88, 90
src/otaclient/create_standby
   __init__.py12558%29–31, 33, 35
   common.py2244480%62, 65–66, 70–72, 74, 78–79, 81, 127, 175–177, 179–181, 183, 186–189, 193, 204, 278–279, 281–286, 298, 335, 363, 366–368, 384–385, 399, 403, 425–426
   interface.py50100% 
   rebuild_mode.py97990%93–95, 107–112
src/otaclient_api/v2
   api_caller.py39684%45–47, 83–85
   api_stub.py170100% 
   types.py2562391%86, 89–92, 131, 209–210, 212, 259, 262–263, 506–508, 512–513, 515, 518–519, 522–523, 586
src/otaclient_common
   __init__.py34876%42–44, 59, 61, 67, 74–75
   common.py1551888%45, 200, 203–205, 220, 227–229, 295–297, 307, 316–318, 364, 368
   downloader.py2001095%110–111, 129, 156, 372, 426, 430, 518–519, 528
   linux.py611575%51–53, 59, 69, 74, 76, 108–109, 133–134, 190, 195–196, 198
   logging.py29196%55
   persist_file_handling.py1181884%113, 118, 150–152, 163, 192–193, 228–232, 242–244, 246–247
   proto_streamer.py42880%33, 48, 66–67, 72, 81–82, 100
   proto_wrapper.py3984887%87, 165, 172, 184–186, 205, 210, 221, 257, 263, 268, 299, 303, 307, 402, 462, 469, 472, 492, 499, 501, 526, 532, 535, 537, 562, 568, 571, 573, 605, 609, 611, 625, 642, 669, 672, 676, 707, 713, 760–763, 765, 803–805
   retry_task_map.py81791%164–165, 167, 179–182
   typing.py25388%69–70, 72
TOTAL5808135176% 

Tests Skipped Failures Errors Time
166 0 💤 0 ❌ 0 🔥 14m 13s ⏱️

Please sign in to comment.