Skip to content

Commit 2eefed0

Browse files
[DPE-3881] Use ruff as a linter and formatter (#162)
## Issue We should use ruff as a linter and formatter ## Solution Use ruff. Fix lint and format warnings
1 parent 2f14032 commit 2eefed0

18 files changed

+150
-413
lines changed

poetry.lock

+22-265
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+35-37
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,13 @@ opentelemetry-exporter-otlp-proto-http = "1.21.0"
3535
optional = true
3636

3737
[tool.poetry.group.format.dependencies]
38-
black = "^24.4.2"
39-
isort = "^5.13.2"
38+
ruff = "^0.4.5"
4039

4140
[tool.poetry.group.lint]
4241
optional = true
4342

4443
[tool.poetry.group.lint.dependencies]
45-
black = "^24.4.2"
46-
isort = "^5.13.2"
47-
flake8 = "^7.0.0"
48-
flake8-docstrings = "^1.7.0"
49-
flake8-copyright = "^0.2.4"
50-
flake8-builtins = "^2.5.0"
51-
pyproject-flake8 = "^7.0.0"
52-
pep8-naming = "^0.14.1"
44+
ruff = "^0.4.5"
5345
codespell = "^2.3.0"
5446

5547
[tool.poetry.group.unit.dependencies]
@@ -85,34 +77,40 @@ log_cli_level = "INFO"
8577
markers = ["unstable"]
8678

8779
# Formatting tools configuration
88-
[tool.black]
80+
[tool.ruff]
81+
# preview and explicit preview are enabled for CPY001
82+
preview = true
83+
target-version = "py38"
84+
src = ["src", "."]
8985
line-length = 99
90-
target-version = ["py38"]
9186

92-
[tool.isort]
93-
profile = "black"
94-
95-
# Linting tools configuration
96-
[tool.flake8]
97-
max-line-length = 99
98-
max-doc-length = 99
99-
max-complexity = 10
100-
exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"]
101-
select = ["E", "W", "F", "C", "N", "R", "D", "H"]
102-
# Ignore W503, E501 because using black creates errors with this
103-
# Ignore D107 Missing docstring in __init__
104-
# Ignore D105 Missing docstring in magic method
105-
# Ignore D415 Docstring first line punctuation (doesn't make sense for properties)
106-
# Ignore D403 First word of the first line should be properly capitalized (false positive on "MySQL")
107-
# Ignore N818 Exception should be named with an Error suffix
108-
# Ignore D102 Missing docstring in public method (pydocstyle doesn't look for docstrings in super class
109-
# Ignore W505 So that strings in comments aren't split across lines
110-
# https://github.com/PyCQA/pydocstyle/issues/309) TODO: add pylint check? https://github.com/PyCQA/pydocstyle/issues/309#issuecomment-1284142716
111-
ignore = ["W503", "E501", "D107", "D105", "D415", "D403", "N818", "D102", "W505"]
87+
[tool.ruff.lint]
88+
explicit-preview-rules = true
89+
select = ["A", "E", "W", "F", "C", "N", "D", "I", "CPY001"]
90+
ignore = [
91+
# Missing docstring in public method (pydocstyle doesn't look for docstrings in super class
92+
# https://github.com/PyCQA/pydocstyle/issues/309) TODO: add pylint check? https://github.com/PyCQA/pydocstyle/issues/309#issuecomment-1284142716
93+
"D102",
94+
"D105", # Missing docstring in magic method
95+
"D107", # Missing docstring in __init__
96+
"D403", # First word of the first line should be capitalized (false positive on "MySQL")
97+
"D415", # Docstring first line punctuation (doesn't make sense for properties)
98+
"E501", # Line too long (because using black creates errors with this)
99+
"N818", # Exception name should be named with an Error suffix
100+
"W505", # Doc line too long (so that strings in comments aren't split across lines)
101+
]
102+
103+
[tool.ruff.lint.per-file-ignores]
112104
# D100, D101, D102, D103: Ignore missing docstrings in tests
113-
per-file-ignores = ["tests/*:D100,D101,D102,D103,D104"]
114-
docstring-convention = "google"
105+
"tests/*" = ["D1"]
106+
107+
[tool.ruff.lint.flake8-copyright]
115108
# Check for properly formatted copyright header in each file
116-
copyright-check = "True"
117-
copyright-author = "Canonical Ltd."
118-
copyright-regexp = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+%(author)s"
109+
author = "Canonical Ltd."
110+
notice-rgx = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+"
111+
112+
[tool.ruff.lint.mccabe]
113+
max-complexity = 10
114+
115+
[tool.ruff.lint.pydocstyle]
116+
convention = "google"

src/container.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def _run_command(
177177
command: typing.List[str],
178178
*,
179179
timeout: typing.Optional[int],
180-
input: str = None,
180+
input: str = None, # noqa: A002 Match subprocess.run()
181181
) -> str:
182182
"""Run command in container.
183183

src/lifecycle.py

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
https://juju.is/docs/sdk/a-charms-life
77
"""
8+
89
import enum
910
import logging
1011
import typing

src/machine_upgrade.py

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Derived from specification: DA058 - In-Place Upgrades - Kubernetes v2
77
(https://docs.google.com/document/d/1tLjknwHudjcHs42nzPVBNkHs98XxAOT2BXGGpP7NyEU/)
88
"""
9+
910
import json
1011
import logging
1112
import time

src/machine_workload.py

+12-16
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,19 @@ def _get_bootstrap_command(
2626
) -> typing.List[str]:
2727
command = super()._get_bootstrap_command(event=event, connection_info=connection_info)
2828
if self._charm.is_externally_accessible(event=event):
29-
command.extend(
30-
[
31-
"--conf-bind-address",
32-
"0.0.0.0",
33-
]
34-
)
29+
command.extend([
30+
"--conf-bind-address",
31+
"0.0.0.0",
32+
])
3533
else:
36-
command.extend(
37-
[
38-
"--conf-use-sockets",
39-
# For unix sockets, authentication fails on first connection if this option is not
40-
# set. Workaround for https://bugs.mysql.com/bug.php?id=107291
41-
"--conf-set-option",
42-
"DEFAULT.server_ssl_mode=PREFERRED",
43-
"--conf-skip-tcp",
44-
]
45-
)
34+
command.extend([
35+
"--conf-use-sockets",
36+
# For unix sockets, authentication fails on first connection if this option is not
37+
# set. Workaround for https://bugs.mysql.com/bug.php?id=107291
38+
"--conf-set-option",
39+
"DEFAULT.server_ssl_mode=PREFERRED",
40+
"--conf-skip-tcp",
41+
])
4642
return command
4743

4844
def _update_configured_socket_file_locations(self) -> None:

src/mysql_shell/__init__.py

+17-20
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,12 @@ def render(connection_info: "relations.database_requires.ConnectionInformation")
8282
error_file = self._container.path("/tmp/mysqlsh_error.json")
8383
temporary_script_file.write_text(script)
8484
try:
85-
self._container.run_mysql_shell(
86-
[
87-
"--no-wizard",
88-
"--python",
89-
"--file",
90-
str(temporary_script_file.relative_to_container),
91-
]
92-
)
85+
self._container.run_mysql_shell([
86+
"--no-wizard",
87+
"--python",
88+
"--file",
89+
str(temporary_script_file.relative_to_container),
90+
])
9391
except container.CalledProcessError as e:
9492
logger.exception(
9593
f"Failed to run MySQL Shell script:\n{logged_script}\n\nstderr:\n{e.stderr}\n"
@@ -105,8 +103,8 @@ def render(connection_info: "relations.database_requires.ConnectionInformation")
105103
raise ShellDBError(**exception)
106104
except ShellDBError as e:
107105
if e.code == 2003:
108-
logger.exception(server_exceptions.ConnectionError.MESSAGE)
109-
raise server_exceptions.ConnectionError
106+
logger.exception(server_exceptions.ConnectionError_.MESSAGE)
107+
raise server_exceptions.ConnectionError_
110108
else:
111109
logger.exception(
112110
f"Failed to run MySQL Shell script:\n{logged_script}\n\nMySQL client error {e.code}\nMySQL Shell traceback:\n{e.traceback_message}\n"
@@ -136,23 +134,22 @@ def create_application_database_and_user(self, *, username: str, database: str)
136134
attributes = self._get_attributes()
137135
logger.debug(f"Creating {database=} and {username=} with {attributes=}")
138136
password = utils.generate_password()
139-
self._run_sql(
140-
[
141-
f"CREATE DATABASE IF NOT EXISTS `{database}`",
142-
f"CREATE USER `{username}` IDENTIFIED BY '{password}' ATTRIBUTE '{attributes}'",
143-
f"GRANT ALL PRIVILEGES ON `{database}`.* TO `{username}`",
144-
]
145-
)
137+
self._run_sql([
138+
f"CREATE DATABASE IF NOT EXISTS `{database}`",
139+
f"CREATE USER `{username}` IDENTIFIED BY '{password}' ATTRIBUTE '{attributes}'",
140+
f"GRANT ALL PRIVILEGES ON `{database}`.* TO `{username}`",
141+
])
146142
logger.debug(f"Created {database=} and {username=} with {attributes=}")
147143
return password
148144

149145
def add_attributes_to_mysql_router_user(
150146
self, *, username: str, router_id: str, unit_name: str
151147
) -> None:
152148
"""Add attributes to user created during MySQL Router bootstrap."""
153-
attributes = self._get_attributes(
154-
{"router_id": router_id, "created_by_juju_unit": unit_name}
155-
)
149+
attributes = self._get_attributes({
150+
"router_id": router_id,
151+
"created_by_juju_unit": unit_name,
152+
})
156153
logger.debug(f"Adding {attributes=} to {username=}")
157154
self._run_sql([f"ALTER USER `{username}` ATTRIBUTE '{attributes}'"])
158155
logger.debug(f"Added {attributes=} to {username=}")

src/relations/hacluster.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ def set_vip(self, vip: Optional[str]) -> None:
9494
json_resources = "{}"
9595
json_resource_params = "{}"
9696

97-
self.relation.data[self.charm.unit].update(
98-
{
99-
"json_resources": json_resources,
100-
"json_resource_params": json_resource_params,
101-
}
102-
)
97+
self.relation.data[self.charm.unit].update({
98+
"json_resources": json_resources,
99+
"json_resource_params": json_resource_params,
100+
})

src/server_exceptions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Error(status_exception.StatusException):
1212
"""MySQL Server unreachable or unhealthy"""
1313

1414

15-
class ConnectionError(Error):
15+
class ConnectionError_(Error): # noqa: N801 for underscore in name
1616
"""MySQL Server unreachable
1717
1818
MySQL client error 2003

src/snap.py

+13-17
Original file line numberDiff line numberDiff line change
@@ -218,23 +218,19 @@ def update_mysql_router_exporter_service(
218218
)
219219

220220
if enabled:
221-
_snap.set(
222-
{
223-
"mysqlrouter-exporter.listen-port": config.listen_port,
224-
"mysqlrouter-exporter.user": config.username,
225-
"mysqlrouter-exporter.password": config.password,
226-
"mysqlrouter-exporter.url": config.url,
227-
"mysqlrouter-exporter.service-name": self._unit_name.replace("/", "-"),
228-
}
229-
)
221+
_snap.set({
222+
"mysqlrouter-exporter.listen-port": config.listen_port,
223+
"mysqlrouter-exporter.user": config.username,
224+
"mysqlrouter-exporter.password": config.password,
225+
"mysqlrouter-exporter.url": config.url,
226+
"mysqlrouter-exporter.service-name": self._unit_name.replace("/", "-"),
227+
})
230228
if tls:
231-
_snap.set(
232-
{
233-
"mysqlrouter.tls-cacert-path": certificate_authority_filename,
234-
"mysqlrouter.tls-cert-path": certificate_filename,
235-
"mysqlrouter.tls-key-path": key_filename,
236-
}
237-
)
229+
_snap.set({
230+
"mysqlrouter.tls-cacert-path": certificate_authority_filename,
231+
"mysqlrouter.tls-cert-path": certificate_filename,
232+
"mysqlrouter.tls-key-path": key_filename,
233+
})
238234
else:
239235
_snap.unset("mysqlrouter.tls-cacert-path")
240236
_snap.unset("mysqlrouter.tls-cert-path")
@@ -261,7 +257,7 @@ def _run_command(
261257
command: typing.List[str],
262258
*,
263259
timeout: typing.Optional[int],
264-
input: str = None,
260+
input: str = None, # noqa: A002 Match subprocess.run()
265261
) -> str:
266262
try:
267263
output = subprocess.run(

src/workload.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,8 @@ def _bootstrap_router(self, *, event, tls: bool) -> None:
291291
elif match := re.fullmatch(r"Error:.*\((?P<code>2[0-9]{3})\)", stderr):
292292
code = int(match.group("code"))
293293
if code == 2003:
294-
logger.error(server_exceptions.ConnectionError.MESSAGE)
295-
raise server_exceptions.ConnectionError from None
294+
logger.error(server_exceptions.ConnectionError_.MESSAGE)
295+
raise server_exceptions.ConnectionError_ from None
296296
else:
297297
logger.error(f"Bootstrap failed with MySQL client error {code}")
298298
raise Exception("Failed to bootstrap router") from None

tests/integration/conftest.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ def juju_has_secrets(mocker: MockerFixture, request):
5252
"""
5353
juju_version = os.environ["LIBJUJU_VERSION_SPECIFIER"].split("/")[0]
5454
if juju_version < "3":
55-
mocker.patch.object(JujuVersion, "has_secrets", new_callable=PropertyMock).return_value = (
56-
False
57-
)
55+
mocker.patch.object(
56+
JujuVersion, "has_secrets", new_callable=PropertyMock
57+
).return_value = False
5858
return False
5959
else:
60-
mocker.patch.object(JujuVersion, "has_secrets", new_callable=PropertyMock).return_value = (
61-
True
62-
)
60+
mocker.patch.object(
61+
JujuVersion, "has_secrets", new_callable=PropertyMock
62+
).return_value = True
6363
return True
6464

6565

tests/integration/helpers.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ async def execute_queries_against_unit(
6666
username: The MySQL username
6767
password: The MySQL password
6868
queries: A list of queries to execute
69+
port: The port to connect to in order to execute queries
6970
commit: A keyword arg indicating whether there are any writes queries
7071
7172
Returns:
@@ -120,9 +121,7 @@ async def delete_file_or_directory_in_unit(ops_test: OpsTest, unit_name: str, pa
120121
if path.strip() in ["/", "."]:
121122
return
122123

123-
return_code, _, _ = await ops_test.juju(
124-
"ssh", unit_name, "sudo", "find", path, "-maxdepth", "1", "-delete"
125-
)
124+
await ops_test.juju("ssh", unit_name, "sudo", "find", path, "-maxdepth", "1", "-delete")
126125

127126

128127
async def write_content_to_file_in_unit(
@@ -192,7 +191,7 @@ async def ls_la_in_unit(ops_test: OpsTest, unit_name: str, directory: str) -> li
192191
Args:
193192
ops_test: The ops test framework
194193
unit_name: The name of unit in which to run ls -la
195-
path: The path from which to run ls -la
194+
directory: The directory from which to run ls -la
196195
197196
Returns:
198197
a list of files returned by ls -la
@@ -403,7 +402,7 @@ async def ensure_all_units_continuous_writes_incrementing(
403402
select_all_continuous_writes_sql,
404403
)
405404
)
406-
numbers = {n for n in range(1, max_written_value)}
405+
numbers = set(range(1, max_written_value))
407406
assert (
408407
numbers <= all_written_values
409408
), f"Missing numbers in database for unit {unit.name}"

tests/integration/test_log_rotation.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,10 @@ async def test_log_rotation(ops_test: OpsTest, mysql_router_charm_series: str) -
153153
len(ls_la_output) == 2
154154
), f"❌ unexpected files/directories in log directory: {ls_la_output}"
155155
directories = [line.split()[-1] for line in ls_la_output]
156-
assert sorted(directories) == sorted(
157-
["mysqlrouter.log", "archive_mysqlrouter"]
158-
), f"❌ unexpected files/directories in log directory: {ls_la_output}"
156+
assert sorted(directories) == sorted([
157+
"mysqlrouter.log",
158+
"archive_mysqlrouter",
159+
]), f"❌ unexpected files/directories in log directory: {ls_la_output}"
159160

160161
logger.info("Ensuring log files was rotated")
161162
file_contents = await read_contents_from_file_in_unit(

0 commit comments

Comments
 (0)