From 4762a014eb90d0019144470b22b639c89e206baa Mon Sep 17 00:00:00 2001 From: Neda Moeini <neda.moeini@geant.org> Date: Tue, 29 Apr 2025 10:53:58 +0200 Subject: [PATCH] Make ruff and mypy happy --- mapping_provider/__init__.py | 4 ++- mapping_provider/api/network_graph.py | 3 +- mapping_provider/app.py | 3 +- mapping_provider/config.py | 43 ++++++++++++++------------- mapping_provider/dependencies.py | 9 +++--- mapping_provider/services/__init__.py | 2 +- mapping_provider/services/gap.py | 33 ++++++++++---------- pyproject.toml | 14 +++++++-- requirements.txt | 2 ++ setup.py | 6 ++-- tox.ini | 27 +++++------------ 11 files changed, 78 insertions(+), 68 deletions(-) diff --git a/mapping_provider/__init__.py b/mapping_provider/__init__.py index 2fdc7a3..e21cff9 100644 --- a/mapping_provider/__init__.py +++ b/mapping_provider/__init__.py @@ -5,6 +5,7 @@ from fastapi import FastAPI from mapping_provider.api.common import router as version_router from mapping_provider.api.network_graph import router as graph_router + def create_app() -> FastAPI: """Create a FastAPI application.""" app = FastAPI( @@ -14,9 +15,10 @@ def create_app() -> FastAPI: # Force configuration to be loaded at startup to avoid issues with missing config from mapping_provider.dependencies import load_config + _ = load_config() app.include_router(version_router) app.include_router(graph_router) - return app \ No newline at end of file + return app diff --git a/mapping_provider/api/network_graph.py b/mapping_provider/api/network_graph.py index fa968b1..3dcc926 100644 --- a/mapping_provider/api/network_graph.py +++ b/mapping_provider/api/network_graph.py @@ -7,5 +7,6 @@ router = APIRouter() @router.get("/network-graph") -def get_network_graph(config: config_dep): +def get_network_graph(config: config_dep) -> dict[str, list[str] | list[list[str]]]: + """Network graph endpoint.""" return {"routers": load_routers(config), "trunks": load_trunks(config)} diff --git a/mapping_provider/app.py b/mapping_provider/app.py index 3889cb0..44befa2 100644 --- a/mapping_provider/app.py +++ b/mapping_provider/app.py @@ -1,9 +1,10 @@ """The main module that runs the application.""" import uvicorn + from mapping_provider import create_app app = create_app() if __name__ == "__main__": - uvicorn.run("mapping_provider.app:app", host="127.0.0.1", port=8000, reload=True) \ No newline at end of file + uvicorn.run("mapping_provider.app:app", host="127.0.0.1", port=8000, reload=True) diff --git a/mapping_provider/config.py b/mapping_provider/config.py index 978b201..181badc 100644 --- a/mapping_provider/config.py +++ b/mapping_provider/config.py @@ -1,43 +1,44 @@ """Configuration file for the Mapping Provider.""" import json +from typing import Any, TextIO import jsonschema CONFIG_SCHEMA = { - '$schema': 'http://json-schema.org/draft-07/schema#', - 'type': 'object', - 'properties': { - 'aai': { - 'type': 'object', - 'properties': { - 'discovery_endpoint_url': {'type': 'string'}, - 'client_id': {'type': 'string'}, - 'secret': {'type': 'string'}, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "aai": { + "type": "object", + "properties": { + "discovery_endpoint_url": {"type": "string"}, + "client_id": {"type": "string"}, + "secret": {"type": "string"}, }, - 'required': ['client_id', 'secret', 'discovery_endpoint_url'], - 'additionalProperties': False + "required": ["client_id", "secret", "discovery_endpoint_url"], + "additionalProperties": False, }, - 'orchestrator': { - 'type': 'object', - 'properties': { - 'url': {'type': 'string'}, + "orchestrator": { + "type": "object", + "properties": { + "url": {"type": "string"}, }, - 'required': ['url'], - 'additionalProperties': False + "required": ["url"], + "additionalProperties": False, }, }, - 'required': ['aai', 'orchestrator'], - 'additionalProperties': False + "required": ["aai", "orchestrator"], + "additionalProperties": False, } -def load(f): +def load(f: TextIO) -> dict[str, Any]: """loads, validates and returns configuration parameters. :param f: file-like object that produces the config file :return: """ - config = json.loads(f.read()) + config: dict[str, Any] = json.loads(f.read()) jsonschema.validate(config, CONFIG_SCHEMA) return config diff --git a/mapping_provider/dependencies.py b/mapping_provider/dependencies.py index a776e79..ac00f54 100644 --- a/mapping_provider/dependencies.py +++ b/mapping_provider/dependencies.py @@ -2,14 +2,15 @@ import os from functools import lru_cache -from typing import Annotated +from typing import Annotated, Any from fastapi import Depends + from mapping_provider import config -@lru_cache() -def load_config() -> dict: +@lru_cache +def load_config() -> dict[str, Any]: """Load and cache the application configuration.""" config_file = os.environ.get("CONFIG_FILE_NAME", "config.json") with open(config_file) as f: @@ -17,4 +18,4 @@ def load_config() -> dict: # Dependency for injecting config into routes/services -config_dep = Annotated[dict, Depends(load_config)] \ No newline at end of file +config_dep = Annotated[dict[str, Any], Depends(load_config)] diff --git a/mapping_provider/services/__init__.py b/mapping_provider/services/__init__.py index e2df2d7..450b588 100644 --- a/mapping_provider/services/__init__.py +++ b/mapping_provider/services/__init__.py @@ -1 +1 @@ -"""External services for the mapping provider.""" \ No newline at end of file +"""External services for the mapping provider.""" diff --git a/mapping_provider/services/gap.py b/mapping_provider/services/gap.py index 42010fb..4b05242 100644 --- a/mapping_provider/services/gap.py +++ b/mapping_provider/services/gap.py @@ -3,6 +3,7 @@ from collections.abc import Callable from typing import Any import requests +from requests import Session from mapping_provider.dependencies import config_dep @@ -12,14 +13,15 @@ GRANT_TYPE = "client_credentials" SCOPE = "openid profile email aarc" -def get_token_endpoint(discovery_endpoint_url: str, session: requests.Session) -> str: +def get_token_endpoint(discovery_endpoint_url: str, session: Session) -> str: """Fetch the token endpoint URL from discovery document.""" response = session.get(discovery_endpoint_url) response.raise_for_status() - return response.json()["token_endpoint"] + data: dict[str, Any] = response.json() + return str(data["token_endpoint"]) -def get_token(config: dict, session: requests.Session) -> str: +def get_token(config: dict[str, Any], session: Session) -> str: """Retrieve an access token using client credentials flow.""" aai_config = config["aai"] token_endpoint = get_token_endpoint(aai_config["discovery_endpoint_url"], session) @@ -33,17 +35,18 @@ def get_token(config: dict, session: requests.Session) -> str: }, ) response.raise_for_status() - return response.json()["access_token"] + data: dict[str, Any] = response.json() + return str(data["access_token"]) -def make_request(query: str, token: str, config: dict, session: requests.Session) -> dict: +def make_request(query: str, token: str, config: dict[str, Any], session: Session) -> dict[Any, Any]: """Make a GraphQL request to the orchestrator API.""" api_url = f"{config['orchestrator']['url']}/api/graphql" headers = {"Authorization": f"Bearer {token}"} response = session.post(api_url, headers=headers, json={"query": query}) response.raise_for_status() - data = response.json() + data: dict[str, Any] = response.json() if "errors" in data: logger.error(f"GraphQL query returned errors: {data['errors']}. Query: {query}") raise ValueError(f"GraphQL query errors: {data['errors']}") @@ -51,16 +54,16 @@ def make_request(query: str, token: str, config: dict, session: requests.Session return data -def extract_router(product_block_instances: list[dict]) -> str | None: +def extract_router(product_block_instances: list[dict[str, Any]]) -> str | None: """Extract router FQDNs from productBlockInstances.""" for instance in product_block_instances: for value in instance.get("productBlockInstanceValues", []): if value.get("field") == "routerFqdn" and value.get("value"): - return value["value"] + return str(value["value"]) return None -def extract_trunk(product_block_instances: list[dict]) -> list[str] | None: +def extract_trunk(product_block_instances: list[dict[str, Any]]) -> list[str] | None: """Extract trunks from productBlockInstances.""" fqdns = [] for instance in product_block_instances: @@ -75,11 +78,11 @@ def extract_trunk(product_block_instances: list[dict]) -> list[str] | None: def load_inventory( - config: dict, - token: str, - tag: str, - session: requests.Session, - extractor: Callable[[list[dict]], Any | None], + config: dict[str, Any], + token: str, + tag: str, + session: Session, + extractor: Callable[[list[dict[str, Any]]], Any | None], ) -> list[Any]: """ Generic function to load inventory items based on tag and extractor function. @@ -143,4 +146,4 @@ def load_trunks(config: config_dep) -> list[list[str]]: """Load trunks (edges) from orchestrator.""" with requests.Session() as session: token = get_token(config, session) - return load_inventory(config, token, tag="IPTRUNK", session=session, extractor=extract_trunk) \ No newline at end of file + return load_inventory(config, token, tag="IPTRUNK", session=session, extractor=extract_trunk) diff --git a/pyproject.toml b/pyproject.toml index cdeac56..b3e6aa6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff] line-length = 120 -target-version = "py313" +target-version = "py312" select = ["E", "F", "I", "B", "UP", "N"] fixable = ["ALL"] exclude = ["tests", "docs", "build"] @@ -9,4 +9,14 @@ exclude = ["tests", "docs", "build"] python_version = "3.13" strict = true warn_unused_ignores = true -warn_return_any = true \ No newline at end of file +warn_return_any = true +allow_untyped_decorators = true +ignore_missing_imports = true +exclude = [ + "venv", + "test/*", + "docs" +] +[[tool.mypy.overrides]] +module = ["requests.*"] +ignore_missing_imports = true \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2f0ae84..05d5101 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ fastapi uvicorn[standard] requests +jsonschema +setuptools sphinx sphinx-rtd-theme diff --git a/setup.py b/setup.py index 5e5209c..32b9b6f 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,9 @@ setup( python_requires=">=3.10", install_requires=[ "fastapi", - "uvicorn[standard]" + "uvicorn[standard]", + "jsonschema", + "requests", ], long_description=open("README.md", encoding="utf-8").read(), long_description_content_type="text/markdown", @@ -23,4 +25,4 @@ setup( "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], -) \ No newline at end of file +) diff --git a/tox.ini b/tox.ini index c34930b..23c347f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,24 +1,11 @@ [tox] -envlist = lint, typecheck, docs +envlist = py313 -[testenv:lint] -description = Lint code with Ruff -deps = ruff -commands = ruff check mapping_provider - -[testenv:typecheck] -description = Type-check code with mypy -deps = mypy -commands = mypy mapping_provider - -[testenv:docs] -description = Build docs +[testenv] deps = - sphinx - sphinx-rtd-theme - sphinxcontrib-plantuml - sphinxcontrib-drawio - sphinxcontrib-openapi -commands = sphinx-build -b html docs/source docs/build - + -r requirements.txt +commands = + ruff check --respect-gitignore --preview . + ruff format --respect-gitignore --preview --check . + mypy . -- GitLab