Skip to content

Commit 0688db4

Browse files
Replace black, flake8 and isort with ruff
1 parent 850c85f commit 0688db4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+377
-1105
lines changed

.github/workflows/ci.yaml

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,6 @@ jobs:
2525
run: make install
2626
- name: Check code format
2727
run: make check-format
28-
format-import:
29-
name: Check imports sorting
30-
runs-on: ubuntu-latest
31-
container: python:3.10-slim
32-
steps:
33-
- name: Checkout repository
34-
uses: actions/checkout@v4
35-
- name: Install required packages
36-
run: apt update && apt install -y ${REQUIRED_PACKAGES}
37-
- name: Install Poetry
38-
run: pip install "${POETRY_SPEC}"
39-
- name: Create virtual environment
40-
run: make install
41-
- name: Check code import format
42-
run: make check-import-sorting
4328
lint-style:
4429
name: Check code style
4530
runs-on: ubuntu-latest

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
_build
33
dist
44
uv.lock
5+
variables.mk

Makefile

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
-include variables.mk
2+
13
FORMAT_DIRS := src stubs tests
24
LINT_DIRS := src tests
35

46
PYTHON ?= poetry run python
57
MYPY ?= poetry run mypy
8+
RUFF ?= poetry run ruff
69

710
.PHONY: install
811
install:
@@ -17,23 +20,19 @@ update:
1720
poetry update --with dev
1821

1922
.PHONY: check
20-
check: check-format check-import-sorting check-poetry check-style check-typing check-docs
23+
check: check-format check-poetry check-style check-typing check-docs
2124

2225
.PHONY: check-format
2326
check-format:
24-
poetry run black --check $(FORMAT_DIRS)
25-
26-
.PHONY: check-import-sorting
27-
check-import-sorting:
28-
poetry run isort --check-only $(FORMAT_DIRS)
27+
$(RUFF) format --check $(FORMAT_DIRS)
2928

3029
.PHONY: check-poetry
3130
check-poetry:
3231
poetry check
3332

3433
.PHONY: check-style
3534
check-style:
36-
poetry run flake8 $(LINT_DIRS)
35+
$(RUFF) check $(LINT_DIRS)
3736

3837
.PHONY: check-typing
3938
check-typing:

docs/conf.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import importlib.metadata
22

3-
4-
project = 'Nitrokey Python SDK'
5-
copyright = '2024, Nitrokey'
6-
author = 'Nitrokey'
7-
release = '0.1.0'
8-
extensions = [
9-
'sphinx.ext.autodoc',
10-
]
11-
html_theme = 'alabaster'
12-
autodoc_class_signature = 'separated'
13-
autodoc_member_order = 'groupwise'
14-
autodoc_typehints = 'description'
3+
project = "Nitrokey Python SDK"
4+
copyright = "2024, Nitrokey"
5+
author = "Nitrokey"
6+
release = "0.1.0"
7+
extensions = ["sphinx.ext.autodoc"]
8+
html_theme = "alabaster"
9+
autodoc_class_signature = "separated"
10+
autodoc_member_order = "groupwise"
11+
autodoc_typehints = "description"
1512

1613

1714
nitrokey_sdk_version = importlib.metadata.version("nitrokey")

poetry.lock

Lines changed: 30 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -39,53 +39,6 @@ files = [
3939
[package.extras]
4040
dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
4141

42-
[[package]]
43-
name = "black"
44-
version = "24.10.0"
45-
description = "The uncompromising code formatter."
46-
optional = false
47-
python-versions = ">=3.9"
48-
groups = ["dev"]
49-
files = [
50-
{file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
51-
{file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
52-
{file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
53-
{file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
54-
{file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
55-
{file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
56-
{file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
57-
{file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
58-
{file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
59-
{file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
60-
{file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
61-
{file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
62-
{file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
63-
{file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
64-
{file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
65-
{file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
66-
{file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"},
67-
{file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"},
68-
{file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"},
69-
{file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"},
70-
{file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
71-
{file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
72-
]
73-
74-
[package.dependencies]
75-
click = ">=8.0.0"
76-
mypy-extensions = ">=0.4.3"
77-
packaging = ">=22.0"
78-
pathspec = ">=0.9.0"
79-
platformdirs = ">=2"
80-
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
81-
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
82-
83-
[package.extras]
84-
colorama = ["colorama (>=0.4.3)"]
85-
d = ["aiohttp (>=3.10)"]
86-
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
87-
uvloop = ["uvloop (>=0.15.2)"]
88-
8942
[[package]]
9043
name = "certifi"
9144
version = "2025.6.15"
@@ -450,23 +403,6 @@ cryptography = ">=2.6,<35 || >35,<45"
450403
[package.extras]
451404
pcsc = ["pyscard (>=1.9,<3)"]
452405

453-
[[package]]
454-
name = "flake8"
455-
version = "7.3.0"
456-
description = "the modular source code checker: pep8 pyflakes and co"
457-
optional = false
458-
python-versions = ">=3.9"
459-
groups = ["dev"]
460-
files = [
461-
{file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"},
462-
{file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"},
463-
]
464-
465-
[package.dependencies]
466-
mccabe = ">=0.7.0,<0.8.0"
467-
pycodestyle = ">=2.14.0,<2.15.0"
468-
pyflakes = ">=3.4.0,<3.5.0"
469-
470406
[[package]]
471407
name = "hidapi"
472408
version = "0.14.0.post4"
@@ -586,21 +522,6 @@ files = [
586522
{file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
587523
]
588524

589-
[[package]]
590-
name = "isort"
591-
version = "5.13.2"
592-
description = "A Python utility / library to sort Python imports."
593-
optional = false
594-
python-versions = ">=3.8.0"
595-
groups = ["dev"]
596-
files = [
597-
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
598-
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
599-
]
600-
601-
[package.extras]
602-
colors = ["colorama (>=0.4.6)"]
603-
604525
[[package]]
605526
name = "jinja2"
606527
version = "3.1.6"
@@ -730,18 +651,6 @@ files = [
730651
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
731652
]
732653

733-
[[package]]
734-
name = "mccabe"
735-
version = "0.7.0"
736-
description = "McCabe checker, plugin for flake8"
737-
optional = false
738-
python-versions = ">=3.6"
739-
groups = ["dev"]
740-
files = [
741-
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
742-
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
743-
]
744-
745654
[[package]]
746655
name = "mdurl"
747656
version = "0.1.2"
@@ -845,23 +754,6 @@ files = [
845754
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
846755
]
847756

848-
[[package]]
849-
name = "platformdirs"
850-
version = "4.3.8"
851-
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
852-
optional = false
853-
python-versions = ">=3.9"
854-
groups = ["dev"]
855-
files = [
856-
{file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"},
857-
{file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"},
858-
]
859-
860-
[package.extras]
861-
docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
862-
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
863-
type = ["mypy (>=1.14.1)"]
864-
865757
[[package]]
866758
name = "protobuf"
867759
version = "5.29.5"
@@ -883,18 +775,6 @@ files = [
883775
{file = "protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84"},
884776
]
885777

886-
[[package]]
887-
name = "pycodestyle"
888-
version = "2.14.0"
889-
description = "Python style guide checker"
890-
optional = false
891-
python-versions = ">=3.9"
892-
groups = ["dev"]
893-
files = [
894-
{file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"},
895-
{file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"},
896-
]
897-
898778
[[package]]
899779
name = "pycparser"
900780
version = "2.22"
@@ -1042,18 +922,6 @@ files = [
1042922
[package.dependencies]
1043923
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
1044924

1045-
[[package]]
1046-
name = "pyflakes"
1047-
version = "3.4.0"
1048-
description = "passive checker of Python programs"
1049-
optional = false
1050-
python-versions = ">=3.9"
1051-
groups = ["dev"]
1052-
files = [
1053-
{file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"},
1054-
{file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"},
1055-
]
1056-
1057925
[[package]]
1058926
name = "pygments"
1059927
version = "2.19.2"
@@ -1176,6 +1044,35 @@ toml = ["tomli (>=2.0) ; python_version <= \"3.10\""]
11761044
type-check = ["mypy (>=1.0)", "types-PyYAML (>=6.0.0)", "types-docutils (>=0.18)"]
11771045
yaml = ["pyyaml (>=6.0.0)"]
11781046

1047+
[[package]]
1048+
name = "ruff"
1049+
version = "0.14.8"
1050+
description = "An extremely fast Python linter and code formatter, written in Rust."
1051+
optional = false
1052+
python-versions = ">=3.7"
1053+
groups = ["dev"]
1054+
files = [
1055+
{file = "ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb"},
1056+
{file = "ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9"},
1057+
{file = "ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f"},
1058+
{file = "ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b"},
1059+
{file = "ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52"},
1060+
{file = "ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c"},
1061+
{file = "ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344"},
1062+
{file = "ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b"},
1063+
{file = "ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a"},
1064+
{file = "ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2"},
1065+
{file = "ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a"},
1066+
{file = "ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b"},
1067+
{file = "ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf"},
1068+
{file = "ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6"},
1069+
{file = "ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e"},
1070+
{file = "ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7"},
1071+
{file = "ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097"},
1072+
{file = "ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99"},
1073+
{file = "ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed"},
1074+
]
1075+
11791076
[[package]]
11801077
name = "semver"
11811078
version = "3.0.4"
@@ -1605,4 +1502,4 @@ files = [
16051502
[metadata]
16061503
lock-version = "2.1"
16071504
python-versions = ">=3.10, <4"
1608-
content-hash = "50d9098be4b28ec32e2a90380e814e870de37ea7947ca366e7c8a7eb864865ad"
1505+
content-hash = "1a20a18c696c423e7b3333b9218c5ca13d6f16ac69168fed5c72a6e0fc362674"

pyproject.toml

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,10 @@ classifiers = [
4545
optional = true
4646

4747
[tool.poetry.group.dev.dependencies]
48-
black = "^24.3"
4948
fake-winreg = "^1.6"
50-
flake8 = "^7.1"
51-
isort = "^5.13.2"
5249
mypy = "^1.4"
5350
rstcheck = { version = "^6", extras = ["sphinx"] }
51+
ruff = "^0.14"
5452
sphinx = "^7"
5553
types-protobuf = ">=5.26, <7"
5654
types-requests = "^2.16"
@@ -74,13 +72,6 @@ dev-dependencies = [
7472
"wrapt <2",
7573
]
7674

77-
[tool.black]
78-
target-version = ["py310"]
79-
80-
[tool.isort]
81-
py_version = "310"
82-
profile = "black"
83-
8475
[tool.mypy]
8576
mypy_path = "stubs"
8677
show_error_codes = true
@@ -90,3 +81,19 @@ strict = true
9081
[tool.rstcheck]
9182
ignore_directives = ["autoclass", "autofunction", "automodule"]
9283
ignore_substitutions = ["nitrokey_sdk_version"]
84+
85+
[tool.ruff]
86+
line-length = 100
87+
88+
[tool.ruff.format]
89+
skip-magic-trailing-comma = true
90+
91+
[tool.ruff.lint]
92+
select = ["B", "C", "E", "F", "W", "I"]
93+
ignore = ["E501"]
94+
95+
[tool.ruff.lint.isort]
96+
split-on-trailing-comma = false
97+
98+
[tool.ruff.lint.mccabe]
99+
max-complexity = 30

src/nitrokey/nk3/_device.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
FIDO2_CERTS = [
1616
Fido2Certs(
1717
start=Version(0, 1, 0),
18-
hashes=[
19-
"ad8fd1d16f59104b9e06ef323cc03f777ed5303cd421a101c9cb00bb3fdf722d",
20-
],
18+
hashes=["ad8fd1d16f59104b9e06ef323cc03f777ed5303cd421a101c9cb00bb3fdf722d"],
2119
),
2220
Fido2Certs(
2321
start=Version(1, 0, 3),

0 commit comments

Comments
 (0)