diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 46717c2b..a6c95ce8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,15 +8,23 @@ repos: - id: end-of-file-fixer - id: requirements-txt-fixer - id: trailing-whitespace +- repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + additional_dependencies: + - tomli +- repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + name: isort (python) - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.4.1 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] -- repo: https://github.com/psf/black - rev: 24.4.1 - hooks: - - id: black + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.9.0 hooks: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7249cda5..87a1a3c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ this operator. ## Developing You can use the environments created by `tox` for development. It helps -install `pre-commit` and `mypy` type checker. +install `pre-commit`, `mypy` type checker, linting tools, and formatting tools. ```shell tox -e dev diff --git a/fmt-requirements.txt b/fmt-requirements.txt index dc109e20..64e5c659 100644 --- a/fmt-requirements.txt +++ b/fmt-requirements.txt @@ -1,2 +1,2 @@ -black +isort ruff diff --git a/lint-requirements.txt b/lint-requirements.txt index 0a78334f..a755aca2 100644 --- a/lint-requirements.txt +++ b/lint-requirements.txt @@ -1,4 +1,3 @@ -black codespell -pre-commit +isort ruff diff --git a/pyproject.toml b/pyproject.toml index 57f8f7e9..4b240ec3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,21 +22,34 @@ show_missing = true minversion = "6.0" log_cli_level = "INFO" -# Formatting tools configuration -[tool.black] -line-length = 99 -target-version = ["py38"] +# Linting and formatting tools configuration +[tool.codespell] +# https://github.com/codespell-project/codespell +skip = ".git,.tox,build,lib,venv,.venv,.mypy_cache,icon.svg,__pycache__" + +[tool.isort] +# Profiles: https://pycqa.github.io/isort/docs/configuration/profiles.html +line_length = 99 +profile = "black" -# Linting tools configuration [tool.ruff] +# Default settings: https://docs.astral.sh/ruff/configuration/ +# Settings: https://docs.astral.sh/ruff/settings/ line-length = 99 -include = ["pyproject.toml", "src/**/*.py", "tests/**/*.py", "lib/charms/glauth_k8s/**/.py"] +include = ["pyproject.toml", "src/**/*.py", "tests/**/*.py", "lib/charms/glauth_k8s/**/*.py"] extend-exclude = ["__pycache__", "*.egg_info"] +preview = true -[too.ruff.lint] -select = ["E", "W", "F", "C", "N", "D", "I001"] -ignore = ["D100", "D101", "D102", "D103", "D105", "D107", "E501", "N818"] -extend-ignore = [ +[tool.ruff.lint] +# Rules: https://docs.astral.sh/ruff/rules/ +select = ["E", "W", "F", "C", "N", "D", "CPY"] +ignore = [ + "D100", + "D101", + "D102", + "D103", + "D105", + "D107", "D203", "D204", "D213", @@ -48,15 +61,21 @@ extend-ignore = [ "D408", "D409", "D413", + "E501", + "N818" ] per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} +[tool.ruff.lint.flake8-copyright] +author = "Canonical Ltd." + [tool.ruff.lint.mccabe] max-complexity = 10 [tool.ruff.lint.pydocstyle] convention = "google" +# Type checking tools configuration [tool.mypy] pretty = true mypy_path = "./src:./lib/:./tests" diff --git a/src/charm.py b/src/charm.py index d0fbd502..dc2bff03 100755 --- a/src/charm.py +++ b/src/charm.py @@ -18,9 +18,23 @@ from charms.glauth_utils.v0.glauth_auxiliary import AuxiliaryProvider, AuxiliaryRequestedEvent from charms.grafana_k8s.v0.grafana_dashboard import GrafanaDashboardProvider from charms.loki_k8s.v0.loki_push_api import LogProxyConsumer, PromtailDigestError -from charms.observability_libs.v1.cert_handler import CertChanged from charms.observability_libs.v0.kubernetes_service_patch import KubernetesServicePatch +from charms.observability_libs.v1.cert_handler import CertChanged from charms.prometheus_k8s.v0.prometheus_scrape import MetricsEndpointProvider +from lightkube import Client +from ops.charm import ( + CharmBase, + ConfigChangedEvent, + HookEvent, + InstallEvent, + PebbleReadyEvent, + RelationJoinedEvent, + RemoveEvent, +) +from ops.main import main +from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus +from ops.pebble import ChangeError + from configs import ConfigFile, DatabaseConfig, StartTLSConfig, pebble_layer from constants import ( CERTIFICATES_INTEGRATION_NAME, @@ -43,19 +57,6 @@ LdapIntegration, ) from kubernetes_resource import ConfigMapResource, StatefulSetResource -from lightkube import Client -from ops.charm import ( - CharmBase, - ConfigChangedEvent, - HookEvent, - InstallEvent, - PebbleReadyEvent, - RelationJoinedEvent, - RemoveEvent, -) -from ops.main import main -from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus -from ops.pebble import ChangeError from utils import ( after_config_updated, block_when, diff --git a/src/configs.py b/src/configs.py index 80f125e4..cff2226d 100644 --- a/src/configs.py +++ b/src/configs.py @@ -1,7 +1,13 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + from dataclasses import asdict, dataclass from pathlib import Path from typing import Any, Mapping, Optional +from jinja2 import Template +from ops.pebble import Layer + from constants import ( GLAUTH_COMMANDS, LOG_FILE, @@ -10,8 +16,6 @@ SERVER_KEY, WORKLOAD_SERVICE, ) -from jinja2 import Template -from ops.pebble import Layer @dataclass @@ -83,20 +87,18 @@ def render(self) -> str: return rendered -pebble_layer = Layer( - { - "summary": "GLAuth layer", - "description": "pebble layer for GLAuth service", - "services": { - WORKLOAD_SERVICE: { - "override": "replace", - "summary": "GLAuth Operator layer", - "startup": "disabled", - "command": '/bin/sh -c "{} 2>&1 | tee {}"'.format( - GLAUTH_COMMANDS, - LOG_FILE, - ), - } - }, - } -) +pebble_layer = Layer({ + "summary": "GLAuth layer", + "description": "pebble layer for GLAuth service", + "services": { + WORKLOAD_SERVICE: { + "override": "replace", + "summary": "GLAuth Operator layer", + "startup": "disabled", + "command": '/bin/sh -c "{} 2>&1 | tee {}"'.format( + GLAUTH_COMMANDS, + LOG_FILE, + ), + } + }, +}) diff --git a/src/database.py b/src/database.py index 9502f6fb..cd7f0c29 100644 --- a/src/database.py +++ b/src/database.py @@ -1,3 +1,6 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + import logging from typing import Any, Optional, Type diff --git a/src/exceptions.py b/src/exceptions.py index 2c1bc532..11969fb7 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -1,3 +1,7 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + + class CharmError(Exception): """Base class for custom charm errors.""" diff --git a/src/integrations.py b/src/integrations.py index 57e93fcf..830cc01f 100644 --- a/src/integrations.py +++ b/src/integrations.py @@ -15,6 +15,10 @@ from charms.glauth_k8s.v0.ldap import LdapProviderBaseData, LdapProviderData from charms.glauth_utils.v0.glauth_auxiliary import AuxiliaryData from charms.observability_libs.v1.cert_handler import CertHandler +from ops.charm import CharmBase +from ops.pebble import PathError +from tenacity import Retrying, retry_if_exception_type, stop_after_attempt, wait_fixed + from configs import DatabaseConfig from constants import ( CERTIFICATE_FILE, @@ -28,14 +32,6 @@ ) from database import Capability, Group, Operation, User from exceptions import CertificatesError -from ops.charm import CharmBase -from ops.pebble import PathError -from tenacity import ( - Retrying, - retry_if_exception_type, - stop_after_attempt, - wait_fixed, -) logger = logging.getLogger(__name__) diff --git a/src/utils.py b/src/utils.py index 5349067c..c291cc87 100644 --- a/src/utils.py +++ b/src/utils.py @@ -5,11 +5,12 @@ from functools import wraps from typing import Any, Callable, Optional -from constants import GLAUTH_CONFIG_FILE, SERVER_CERT, SERVER_KEY from ops.charm import CharmBase, EventBase from ops.model import BlockedStatus, WaitingStatus from tenacity import Retrying, TryAgain, wait_fixed +from constants import GLAUTH_CONFIG_FILE, SERVER_CERT, SERVER_KEY + logger = logging.getLogger(__name__) ConditionEvaluation = tuple[bool, str] diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index d5d8562b..af9b30f5 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -6,17 +6,18 @@ from unittest.mock import MagicMock import pytest -from charm import GLAuthCharm from charms.glauth_k8s.v0.ldap import LdapProviderData +from ops.charm import CharmBase +from ops.testing import Harness +from pytest_mock import MockerFixture + +from charm import GLAuthCharm from constants import ( CERTIFICATES_INTEGRATION_NAME, CERTIFICATES_TRANSFER_INTEGRATION_NAME, DATABASE_INTEGRATION_NAME, WORKLOAD_CONTAINER, ) -from ops.charm import CharmBase -from ops.testing import Harness -from pytest_mock import MockerFixture DB_APP = "postgresql-k8s" DB_DATABASE = "glauth" diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index d6a7bf90..efb8020e 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -4,18 +4,15 @@ from unittest.mock import MagicMock, patch import pytest -from conftest import ( - LDAP_AUXILIARY_APP, - LDAP_CLIENT_APP, - LDAP_PROVIDER_DATA, -) -from constants import WORKLOAD_CONTAINER, WORKLOAD_SERVICE -from exceptions import CertificatesError -from kubernetes_resource import KubernetesResourceError +from conftest import LDAP_AUXILIARY_APP, LDAP_CLIENT_APP, LDAP_PROVIDER_DATA from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus from ops.testing import Harness from pytest_mock import MockerFixture +from constants import WORKLOAD_CONTAINER, WORKLOAD_SERVICE +from exceptions import CertificatesError +from kubernetes_resource import KubernetesResourceError + class TestInstallEvent: def test_on_install_non_leader_unit( diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 8739c321..fe3a6320 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -4,10 +4,11 @@ from io import StringIO from unittest.mock import MagicMock, PropertyMock, patch, sentinel -from constants import DATABASE_INTEGRATION_NAME, WORKLOAD_CONTAINER from ops.charm import CharmBase, HookEvent from ops.model import ActiveStatus, BlockedStatus, WaitingStatus from ops.testing import Harness + +from constants import DATABASE_INTEGRATION_NAME, WORKLOAD_CONTAINER from utils import ( after_config_updated, block_when, diff --git a/tox.ini b/tox.ini index 41cd3619..72918e25 100644 --- a/tox.ini +++ b/tox.ini @@ -28,37 +28,29 @@ deps = pre-commit mypy types-PyYAML + -r{toxinidir}/fmt-requirements.txt + -r{toxinidir}/lint-requirements.txt commands = pre-commit install -t commit-msg [testenv:fmt] -description = Apply coding style standards to code +description = Apply coding style standards deps = -r{toxinidir}/fmt-requirements.txt commands = - black {[vars]all_path} - ruff check --fix {[vars]all_path} + isort {[vars]all_path} + ruff format {[vars]all_path} [testenv:lint] description = Check code against coding style standards deps = + ; The tomli package is needed because https://github.com/codespell-project/codespell?tab=readme-ov-file#using-a-config-file + tomli -r{toxinidir}/lint-requirements.txt commands = - codespell {[vars]lib_path} - codespell {toxinidir} \ - --skip {toxinidir}/.git \ - --skip {toxinidir}/.tox \ - --skip {toxinidir}/build \ - --skip {toxinidir}/lib \ - --skip {toxinidir}/venv \ - --skip {toxinidir}/.mypy_cache \ - --skip {toxinidir}/icon.svg - - ruff check {[vars]all_path} - black --check --diff {[vars]all_path} - - pre-commit install - pre-commit autoupdate + codespell {toxinidir}/ + isort --check-only --diff {[vars]all_path} + ruff check --show-fixes {[vars]all_path} [testenv:unit] description = Run unit tests