Skip to content

Commit ae2e8eb

Browse files
[DPE-3881] Use ruff as a linter and formatter (#292)
## Issue We would like to use ruff as a linter and formatter ## Solution Use ruff as a linter and formatter. Run format on the repo and push up changes as a result of the format
1 parent 266066c commit ae2e8eb

16 files changed

+161
-414
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-38
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,13 @@ jsonschema = "*"
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,35 +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-
known_third_party = "mysql.connector"
95-
96-
# Linting tools configuration
97-
[tool.flake8]
98-
max-line-length = 99
99-
max-doc-length = 99
100-
max-complexity = 10
101-
exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"]
102-
select = ["E", "W", "F", "C", "N", "R", "D", "H"]
103-
# Ignore W503, E501 because using black creates errors with this
104-
# Ignore D107 Missing docstring in __init__
105-
# Ignore D105 Missing docstring in magic method
106-
# Ignore D415 Docstring first line punctuation (doesn't make sense for properties)
107-
# Ignore D403 First word of the first line should be properly capitalized (false positive on "MySQL")
108-
# Ignore N818 Exception should be named with an Error suffix
109-
# Ignore D102 Missing docstring in public method (pydocstyle doesn't look for docstrings in super class
110-
# Ignore W505 So that strings in comments aren't split across lines
111-
# https://github.com/PyCQA/pydocstyle/issues/309) TODO: add pylint check? https://github.com/PyCQA/pydocstyle/issues/309#issuecomment-1284142716
112-
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]
113104
# D100, D101, D102, D103: Ignore missing docstrings in tests
114-
per-file-ignores = ["tests/*:D100,D101,D102,D103,D104"]
115-
docstring-convention = "google"
105+
"tests/*" = ["D1"]
106+
107+
[tool.ruff.lint.flake8-copyright]
116108
# Check for properly formatted copyright header in each file
117-
copyright-check = "True"
118-
copyright-author = "Canonical Ltd."
119-
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/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/cos.py

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# See LICENSE file for licensing details.
33

44
"""Relation to the cos charms."""
5+
56
import logging
67
import typing
78
from dataclasses import dataclass

src/rock.py

+44-48
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,18 @@ def update_mysql_router_service(self, *, enabled: bool, tls: bool = None) -> Non
116116
startup = ops.pebble.ServiceStartup.ENABLED.value
117117
else:
118118
startup = ops.pebble.ServiceStartup.DISABLED.value
119-
layer = ops.pebble.Layer(
120-
{
121-
"services": {
122-
self._SERVICE_NAME: {
123-
"override": "replace",
124-
"summary": "MySQL Router",
125-
"command": command,
126-
"startup": startup,
127-
"user": _UNIX_USERNAME,
128-
"group": _UNIX_USERNAME,
129-
},
119+
layer = ops.pebble.Layer({
120+
"services": {
121+
self._SERVICE_NAME: {
122+
"override": "replace",
123+
"summary": "MySQL Router",
124+
"command": command,
125+
"startup": startup,
126+
"user": _UNIX_USERNAME,
127+
"group": _UNIX_USERNAME,
130128
},
131-
}
132-
)
129+
},
130+
})
133131
self._container.add_layer(self._SERVICE_NAME, layer, combine=True)
134132
# `self._container.replan()` does not stop services that have been disabled
135133
# Use `restart()` and `stop()` instead
@@ -168,32 +166,28 @@ def update_mysql_router_exporter_service(
168166
"MYSQLROUTER_EXPORTER_SERVICE_NAME": self._unit_name.replace("/", "-"),
169167
}
170168
if tls:
171-
environment.update(
172-
{
173-
"MYSQLROUTER_TLS_CACERT_PATH": certificate_authority_filename,
174-
"MYSQLROUTER_TLS_CERT_PATH": certificate_filename,
175-
"MYSQLROUTER_TLS_KEY_PATH": key_filename,
176-
}
177-
)
169+
environment.update({
170+
"MYSQLROUTER_TLS_CACERT_PATH": certificate_authority_filename,
171+
"MYSQLROUTER_TLS_CERT_PATH": certificate_filename,
172+
"MYSQLROUTER_TLS_KEY_PATH": key_filename,
173+
})
178174
else:
179175
startup = ops.pebble.ServiceStartup.DISABLED.value
180176
environment = {}
181177

182-
layer = ops.pebble.Layer(
183-
{
184-
"services": {
185-
self._EXPORTER_SERVICE_NAME: {
186-
"override": "replace",
187-
"summary": "MySQL Router Exporter",
188-
"command": "/start-mysql-router-exporter.sh",
189-
"startup": startup,
190-
"user": _UNIX_USERNAME,
191-
"group": _UNIX_USERNAME,
192-
"environment": environment,
193-
},
178+
layer = ops.pebble.Layer({
179+
"services": {
180+
self._EXPORTER_SERVICE_NAME: {
181+
"override": "replace",
182+
"summary": "MySQL Router Exporter",
183+
"command": "/start-mysql-router-exporter.sh",
184+
"startup": startup,
185+
"user": _UNIX_USERNAME,
186+
"group": _UNIX_USERNAME,
187+
"environment": environment,
194188
},
195-
}
196-
)
189+
},
190+
})
197191
self._container.add_layer(self._EXPORTER_SERVICE_NAME, layer, combine=True)
198192
# `self._container.replan()` does not stop services that have been disabled
199193
# Use `restart()` and `stop()` instead
@@ -216,20 +210,18 @@ def update_logrotate_executor_service(self, *, enabled: bool) -> None:
216210
if enabled
217211
else ops.pebble.ServiceStartup.DISABLED.value
218212
)
219-
layer = ops.pebble.Layer(
220-
{
221-
"services": {
222-
self._LOGROTATE_EXECUTOR_SERVICE_NAME: {
223-
"override": "replace",
224-
"summary": "Logrotate executor",
225-
"command": "python3 /logrotate_executor.py",
226-
"startup": startup,
227-
"user": _UNIX_USERNAME,
228-
"group": _UNIX_USERNAME,
229-
},
213+
layer = ops.pebble.Layer({
214+
"services": {
215+
self._LOGROTATE_EXECUTOR_SERVICE_NAME: {
216+
"override": "replace",
217+
"summary": "Logrotate executor",
218+
"command": "python3 /logrotate_executor.py",
219+
"startup": startup,
220+
"user": _UNIX_USERNAME,
221+
"group": _UNIX_USERNAME,
230222
},
231-
}
232-
)
223+
},
224+
})
233225
self._container.add_layer(self._LOGROTATE_EXECUTOR_SERVICE_NAME, layer, combine=True)
234226
# `self._container.replan()` does not stop services that have been disabled
235227
# Use `restart()` and `stop()` instead
@@ -240,7 +232,11 @@ def update_logrotate_executor_service(self, *, enabled: bool) -> None:
240232

241233
# TODO python3.10 min version: Use `list` instead of `typing.List`
242234
def _run_command(
243-
self, command: typing.List[str], *, timeout: typing.Optional[int], input: str = None
235+
self,
236+
command: typing.List[str],
237+
*,
238+
timeout: typing.Optional[int],
239+
input: str = None, # noqa: A002 Match subprocess.run()
244240
) -> str:
245241
try:
246242
process = self._container.exec(

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/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/helpers.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ async def delete_file_or_directory_in_unit(
157157
if path.strip() in ["/", "."]:
158158
return
159159

160-
return_code, _, _ = await ops_test.juju(
160+
await ops_test.juju(
161161
"ssh",
162162
"--container",
163163
container_name,
@@ -274,7 +274,7 @@ async def ls_la_in_unit(
274274
Args:
275275
ops_test: The ops test framework
276276
unit_name: The name of unit in which to run ls -la
277-
path: The path from which to run ls -la
277+
directory: The directory from which to run ls -la
278278
container_name: The container where to run ls -la
279279
280280
Returns:
@@ -592,7 +592,7 @@ async def ensure_all_units_continuous_writes_incrementing(
592592
select_all_continuous_writes_sql,
593593
)
594594
)
595-
numbers = {n for n in range(1, max_written_value)}
595+
numbers = set(range(1, max_written_value))
596596
assert (
597597
numbers <= all_written_values
598598
), 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
@@ -140,9 +140,10 @@ async def test_log_rotation(ops_test: OpsTest):
140140
len(ls_la_output) == 2
141141
), f"❌ unexpected files/directories in log directory: {ls_la_output}"
142142
directories = [line.split()[-1] for line in ls_la_output]
143-
assert sorted(directories) == sorted(
144-
["mysqlrouter.log", "archive_mysqlrouter"]
145-
), f"❌ unexpected files/directories in log directory: {ls_la_output}"
143+
assert sorted(directories) == sorted([
144+
"mysqlrouter.log",
145+
"archive_mysqlrouter",
146+
]), f"❌ unexpected files/directories in log directory: {ls_la_output}"
146147

147148
logger.info("Ensuring log files was rotated")
148149
file_contents = await read_contents_from_file_in_unit(

0 commit comments

Comments
 (0)