From f4c8e1062bd26352c3a66f086d47b25b221593be Mon Sep 17 00:00:00 2001 From: Karel van Klink <karel.vanklink@geant.org> Date: Tue, 21 Nov 2023 17:41:31 +0100 Subject: [PATCH] rework pipeline to use ruff instead of flake, isort, and black --- .pylintrc | 9 ---- docs/dump-openapi-spec.py | 16 +++--- docs/source/conf.py | 47 ++++++++-------- lso/__init__.py | 2 +- lso/app.py | 1 + lso/environment.py | 12 +++-- lso/playbook.py | 10 +++- lso/routes/__init__.py | 1 + lso/routes/default.py | 1 + lso/routes/ip_trunk.py | 2 +- pyproject.toml | 101 +++++++++++++++++------------------ requirements.txt | 3 -- setup.py | 7 ++- test/conftest.py | 11 ++-- test/routes/test_ip_trunk.py | 21 +++++--- test/routes/test_router.py | 7 ++- test/test_config.py | 8 ++- tox.ini | 17 +----- 18 files changed, 138 insertions(+), 138 deletions(-) delete mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index d5b8313..0000000 --- a/.pylintrc +++ /dev/null @@ -1,9 +0,0 @@ -[MAIN] -extension-pkg-whitelist=pydantic - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# Note that it does not contain TODO, only the default FIXME and XXX -notes=FIXME, - XXX diff --git a/docs/dump-openapi-spec.py b/docs/dump-openapi-spec.py index 944838a..8e35e71 100644 --- a/docs/dump-openapi-spec.py +++ b/docs/dump-openapi-spec.py @@ -4,21 +4,19 @@ import os from fastapi.testclient import TestClient import lso -config_filename = os.path.join( - os.path.dirname(__file__), - '..', 'config.json.example') +config_filename = os.path.join(os.path.dirname(__file__), "..", "config.json.example") output_filename = os.path.join( - os.path.dirname(__file__), - 'source', '_static', 'openapi.json') + os.path.dirname(__file__), "source", "_static", "openapi.json" +) -os.environ['SETTINGS_FILENAME'] = config_filename +os.environ["SETTINGS_FILENAME"] = config_filename app = lso.create_app() client = TestClient(app) -rsp = client.get('/openapi.json') +rsp = client.get("/openapi.json") openapi_doc = json.dumps(rsp.json(), indent=2) -with open(output_filename, 'w') as f: +with open(output_filename, "w") as f: f.write(openapi_doc) -print(f'wrote {output_filename}') +print(f"wrote {output_filename}") diff --git a/docs/source/conf.py b/docs/source/conf.py index 645bdfe..1be1927 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,10 +16,9 @@ import json import os import sys -sys.path.insert(0, os.path.abspath( - os.path.join( - os.path.dirname(__file__), - '..', '..', 'lso'))) +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "lso")) +) class RenderAsJSON(Directive): @@ -28,56 +27,56 @@ class RenderAsJSON(Directive): required_arguments = 1 def run(self): - module_path, member_name = self.arguments[0].rsplit('.', 1) + module_path, member_name = self.arguments[0].rsplit(".", 1) member_data = getattr(import_module(module_path), member_name) code = json.dumps(member_data, indent=2) literal = nodes.literal_block(code, code) - literal['language'] = 'json' + literal["language"] = "json" return [ - addnodes.desc_name(text=member_name), - addnodes.desc_content('', literal) + addnodes.desc_name(text=member_name), + addnodes.desc_content("", literal), ] def setup(app): - app.add_directive('asjson', RenderAsJSON) + app.add_directive("asjson", RenderAsJSON) # -- Project information ----------------------------------------------------- -project = 'Lightweight Service Orchestrator' -copyright = '2023, GÉANT Vereniging' -author = 'GÉANT Orchestration & Automation Team' +project = "Lightweight Service Orchestrator" +copyright = "2023, GÉANT Vereniging" +author = "GÉANT Orchestration & Automation Team" # -- General configuration --------------------------------------------------- extensions = [ - 'sphinx_rtd_theme', - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', - 'sphinx.ext.todo' + "sphinx_rtd_theme", + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.todo", ] -templates_path = ['templates'] +templates_path = ["templates"] exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- -html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] html_theme_options = { - 'style_nav_header_background': 'rgb(0 63 95)', + "style_nav_header_background": "rgb(0 63 95)", } -html_css_files = ['custom.css'] -html_logo = '_static/geant_logo_white.svg' +html_css_files = ["custom.css"] +html_logo = "_static/geant_logo_white.svg" # Both the class' and the ``__init__`` method's docstring are concatenated and inserted. -autoclass_content = 'both' -autodoc_typehints = 'none' +autoclass_content = "both" +autodoc_typehints = "none" # Display todos by setting to True todo_include_todos = True diff --git a/lso/__init__.py b/lso/__init__.py index 1faa2be..03ff9e5 100644 --- a/lso/__init__.py +++ b/lso/__init__.py @@ -1,4 +1,5 @@ """Automatically invoked app factory.""" + import logging from fastapi import FastAPI @@ -13,7 +14,6 @@ def create_app() -> FastAPI: :return: a new flask app instance """ - app = FastAPI() # app = FastAPI(dependencies=[Depends(get_query_token)]) diff --git a/lso/app.py b/lso/app.py index 1c28f76..8ca6862 100644 --- a/lso/app.py +++ b/lso/app.py @@ -1,4 +1,5 @@ """Default app creation.""" + import lso app = lso.create_app() diff --git a/lso/environment.py b/lso/environment.py index bd547e7..5d02d16 100644 --- a/lso/environment.py +++ b/lso/environment.py @@ -7,16 +7,22 @@ import os LOGGING_DEFAULT_CONFIG = { "version": 1, "disable_existing_loggers": False, - "formatters": {"simple": {"format": "%(asctime)s - %(name)s " "(%(lineno)d) - %(levelname)s - %(message)s"}}, + "formatters": {"simple": {"format": "%(asctime)s - %(name)s (%(lineno)d) - %(levelname)s - %(message)s"}}, "handlers": { "console": { "class": "logging.StreamHandler", "level": "DEBUG", "formatter": "simple", "stream": "ext://sys.stdout", - } + }, + }, + "loggers": { + "resource_management": { + "level": "DEBUG", + "handlers": ["console"], + "propagate": False, + }, }, - "loggers": {"resource_management": {"level": "DEBUG", "handlers": ["console"], "propagate": False}}, "root": {"level": "INFO", "handlers": ["console"]}, } diff --git a/lso/playbook.py b/lso/playbook.py index 9650985..d04737c 100644 --- a/lso/playbook.py +++ b/lso/playbook.py @@ -1,4 +1,5 @@ """Module that gathers common API responses and data models.""" + import enum import json import logging @@ -137,7 +138,13 @@ def _process_json_output(runner: ansible_runner.Runner) -> list[dict[Any, Any]]: return parsed_output -def _run_playbook_proc(job_id: str, playbook_path: str, extra_vars: dict, inventory: list[str], callback: str) -> None: +def _run_playbook_proc( + job_id: str, + playbook_path: str, + extra_vars: dict, + inventory: list[str], + callback: str, +) -> None: """Run a playbook, internal function. :param str job_id: Identifier of the job that's executed. @@ -177,7 +184,6 @@ def run_playbook(playbook_path: str, extra_vars: dict, inventory: str, callback: :return: Result of playbook launch, this could either be successful or unsuccessful. :rtype: :class:`PlaybookLaunchResponse` """ - job_id = str(uuid.uuid4()) thread = threading.Thread( target=_run_playbook_proc, diff --git a/lso/routes/__init__.py b/lso/routes/__init__.py index e69de29..6df1dee 100644 --- a/lso/routes/__init__.py +++ b/lso/routes/__init__.py @@ -0,0 +1 @@ +"""Module of all routes that are available in LSO.""" diff --git a/lso/routes/default.py b/lso/routes/default.py index 2d7ddb1..d6f7292 100644 --- a/lso/routes/default.py +++ b/lso/routes/default.py @@ -2,6 +2,7 @@ For now only includes a single endpoint that responds with the current version of the API and LSO. """ + from importlib import metadata from fastapi import APIRouter diff --git a/lso/routes/ip_trunk.py b/lso/routes/ip_trunk.py index 3531cdc..086ec21 100644 --- a/lso/routes/ip_trunk.py +++ b/lso/routes/ip_trunk.py @@ -28,7 +28,7 @@ class IPTrunkProvisioningParams(IPTrunkParams): #: also making it an optional parameter. dry_run: bool | None = True #: The type of object that is changed. - object: str + object: str # noqa: A003 class IPTrunkModifyParams(IPTrunkParams): diff --git a/pyproject.toml b/pyproject.toml index 99627a6..4be30a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,29 +1,7 @@ -[tool.isort] -profile = "black" -line_length = 120 -skip = ["venv", ".tox", "docs"] -known_third_party = ["pydantic", "migrations"] -known_first_party = ["test", "docs"] - -[tool.black] -line-length = 120 -target-version = ["py310"] -exclude = ''' -( - /( - geant_service_orchestrator\.egg-info # exclude a few common directories in the - | \.git # root of the project - | \.*_cache - | \.tox - | venv - | docs - )/ -) -''' - [tool.mypy] exclude = [ "venv", + "test/*", "docs" ] ignore_missing_imports = true @@ -44,64 +22,81 @@ show_error_codes = true show_column_numbers = true # Suppress "note: By default the bodies of untyped functions are not checked" disable_error_code = "annotation-unchecked" +# Forbid the use of a generic "type: ignore" without specifying the exact error that is ignored +enable_error_code = "ignore-without-code" [tool.ruff] -exclude = [ - ".git", - ".*_cache", - ".tox", - "*.egg-info", - "__pycache__", +extend-exclude = [ "htmlcov", - "venv", - "docs" + "docs", ] ignore = [ - "C417", - "D100", - "D101", - "D102", - "D103", - "D104", - "D105", - "D106", - "D107", - "D202", "D203", "D213", - "E501", - "N806", - "B905", "N805", - "B904", - "N803", - "N801", - "N815", - "N802", - "S101", - "S104" + "PLR0913", + "PLR0904", + "PLW1514" ] line-length = 120 select = [ + "A", + "ARG", "B", + "BLE", "C", + "COM", + "C4", + "C90", "D", + "DTZ", "E", + "EM", + "ERA", "F", + "FA", + "FBT", + "FLY", + "FURB", + "G", "I", + "ICN", + "INP", + "ISC", + "LOG", "N", + "PERF", + "PGH", + "PIE", + "PL", + "PT", + "PTH", + "PYI", + "Q", + "RET", + "R", "RET", + "RSE", + "RUF", "S", + "SIM", + "SLF", "T", + "T20", + "TID", + "TRY", + "UP", "W", + "YTT" ] -target-version = "py310" +target-version = "py311" [tool.ruff.flake8-tidy-imports] ban-relative-imports = "all" [tool.ruff.per-file-ignores] -"test/*" = ["B033", "N816", "N802"] +"test/*" = ["D", "S101", "PLR2004"] +"setup.py" = ["D100"] [tool.ruff.isort] known-third-party = ["pydantic", "migrations"] diff --git a/requirements.txt b/requirements.txt index ce50fc8..6b03e31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,9 +25,6 @@ sphinx~=7.2.6 sphinx-rtd-theme~=1.3.0 requests~=2.31.0 docutils~=0.18.1 -isort~=5.12.0 -black~=23.11.0 -flake8~=6.1.0 mypy~=1.7.0 ruff~=0.1.6 types-setuptools~=68.2.0.1 diff --git a/setup.py b/setup.py index 24315c2..72087cc 100644 --- a/setup.py +++ b/setup.py @@ -4,9 +4,9 @@ setup( name="goat-lso", version="0.1", author="GÉANT Orchestration & Automation Team", - author_email="TBD", + author_email="goat@geant.org", description="Lightweight Service Orchestrator", - url="https://gitlab.geant.org/goat/gap/lso", + url="https://gitlab.software.geant.org/goat/gap/lso", packages=find_packages(), install_requires=[ "jsonschema~=4.18.0", @@ -34,11 +34,10 @@ setup( license_files=("LICENSE.txt",), classifiers=[ "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Development Status :: 2 - Pre-Alpha", ], - python_requires=">=3.10", + python_requires=">=3.11", ) diff --git a/test/conftest.py b/test/conftest.py index 2e35869..cb4725e 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,8 +1,9 @@ import json import os import tempfile +from collections.abc import Callable, Generator from io import StringIO -from typing import Any, Callable, Generator +from typing import Any import pytest from faker import Faker @@ -11,7 +12,7 @@ from fastapi.testclient import TestClient import lso -@pytest.fixture +@pytest.fixture() def mocked_ansible_runner_run() -> Callable: class Runner: def __init__(self) -> None: @@ -19,7 +20,7 @@ def mocked_ansible_runner_run() -> Callable: self.rc = 0 self.stdout = StringIO("[{'step one': 'results'}, {'step two': 2}]") - def run(*args: Any, **kwargs: Any) -> Runner: + def run(*args: Any, **kwargs: Any) -> Runner: # noqa: ARG001 return Runner() return run @@ -44,11 +45,11 @@ def data_config_filename(configuration_data: dict[str, str]) -> Generator[str, A @pytest.fixture(scope="session") -def client(data_config_filename: str) -> Generator[TestClient, Any, None]: +def client(data_config_filename: str) -> TestClient: """Return a client that can be used to test the server.""" os.environ["SETTINGS_FILENAME"] = data_config_filename app = lso.create_app() - yield TestClient(app) # wait here until calling context ends + return TestClient(app) # wait here until calling context ends @pytest.fixture(scope="session") diff --git a/test/routes/test_ip_trunk.py b/test/routes/test_ip_trunk.py index 21f74ca..9b931bb 100644 --- a/test/routes/test_ip_trunk.py +++ b/test/routes/test_ip_trunk.py @@ -1,5 +1,5 @@ import time -from typing import Callable +from collections.abc import Callable from unittest.mock import patch import jsonschema @@ -66,7 +66,10 @@ def subscription_object(faker: Faker) -> dict: "iptrunk_side_ae_members": ["ge-0/0/0", "ge-0/0/1"], "subscription_instance_id": faker.uuid4(), "iptrunk_side_ae_geant_a_sid": "SID-11112", - "iptrunk_side_ae_members_description": [faker.pystr(), faker.pystr()], + "iptrunk_side_ae_members_description": [ + faker.pystr(), + faker.pystr(), + ], }, { "name": "IptrunkSideBlock", @@ -110,7 +113,10 @@ def subscription_object(faker: Faker) -> dict: "iptrunk_side_ae_members": ["ge-0/0/0", "ge-0/0/1"], "subscription_instance_id": faker.uuid4(), "iptrunk_side_ae_geant_a_sid": "SID-11112", - "iptrunk_side_ae_members_description": [faker.pystr(), faker.pystr()], + "iptrunk_side_ae_members_description": [ + faker.pystr(), + faker.pystr(), + ], }, ], }, @@ -160,7 +166,7 @@ def migration_object(faker: Faker) -> dict: @responses.activate def test_ip_trunk_provisioning( - client: TestClient, subscription_object: dict, mocked_ansible_runner_run: Callable + client: TestClient, subscription_object: dict, mocked_ansible_runner_run: Callable, ) -> None: responses.post(url=TEST_CALLBACK_URL, status=200) @@ -189,7 +195,7 @@ def test_ip_trunk_provisioning( @responses.activate def test_ip_trunk_modification( - client: TestClient, subscription_object: dict, mocked_ansible_runner_run: Callable + client: TestClient, subscription_object: dict, mocked_ansible_runner_run: Callable, ) -> None: responses.post(url=TEST_CALLBACK_URL, status=200) @@ -244,7 +250,10 @@ def test_ip_trunk_deletion(client: TestClient, subscription_object: dict, mocked @responses.activate def test_ip_trunk_migration( - client: TestClient, subscription_object: dict, migration_object: dict, mocked_ansible_runner_run: Callable + client: TestClient, + subscription_object: dict, + migration_object: dict, + mocked_ansible_runner_run: Callable, ) -> None: responses.post(url=TEST_CALLBACK_URL, status=204) diff --git a/test/routes/test_router.py b/test/routes/test_router.py index 6a9f2ff..f0706b6 100644 --- a/test/routes/test_router.py +++ b/test/routes/test_router.py @@ -1,5 +1,5 @@ import time -from typing import Callable +from collections.abc import Callable from unittest.mock import patch import jsonschema @@ -31,7 +31,10 @@ def test_router_provisioning(client: TestClient, faker: Faker, mocked_ansible_ru "lo_iso_address": "1.2.3.4.5.6", "snmp_location": "city,country[1.2,3.4]", "si_ipv4_network": faker.ipv4() + "/24", - "ias_lt_network": {"v4": faker.ipv4() + "/24", "v6": faker.ipv6() + "/64"}, + "ias_lt_network": { + "v4": faker.ipv4() + "/24", + "v6": faker.ipv6() + "/64", + }, "site_country_code": faker.country_code(), "site_city": faker.city(), "site_latitude": float(faker.latitude()), diff --git a/test/test_config.py b/test/test_config.py index bb724c7..c1d54d8 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,4 +1,5 @@ """Set of tests that verify correct config is accepted and incorrect config is not.""" + import io import json import os @@ -20,7 +21,12 @@ def test_validate_testenv_config(data_config_filename: str) -> None: @pytest.mark.parametrize( - "bad_config", [{"name": "bad version", "version": 123}, {"name": "missing version"}, {"version": "missing name"}] + "bad_config", + [ + {"name": "bad version", "version": 123}, + {"name": "missing version"}, + {"version": "missing name"}, + ], ) def test_bad_config(bad_config: dict) -> None: with io.StringIO(json.dumps(bad_config)) as file: diff --git a/tox.ini b/tox.ini index 927b897..3468ba6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,6 @@ [tox] envlist = py311 -[flake8] -ignore = W503 -exclude = .git,.*_cache,.eggs,*.egg-info,__pycache__,venv,.tox,docs -enable-extensions = G -select = B,C,D,E,F,G,I,N,S,T,W,B902,B903,R -max-line-length = 120 -ban-relative-imports = true -per-file-ignores = - # Allow first argument to be cls instead of self for pydantic validators - gso/*: B902 - [testenv] passenv = XDG_CACHE_HOME,USE_COMPOSE setenv = @@ -21,11 +10,9 @@ deps = -r requirements.txt commands = - isort -c . - ruff . - black --check . + ruff --respect-gitignore --preview . + ruff format --respect-gitignore --preview --check . mypy . - flake8 coverage erase coverage run --source lso -m pytest coverage xml -- GitLab