Skip to content
Snippets Groups Projects
Commit 4762a014 authored by Neda Moeini's avatar Neda Moeini
Browse files

Make ruff and mypy happy

parent 34d60e17
Branches
No related tags found
1 merge request!1Feature/get graph data from gap
...@@ -5,6 +5,7 @@ from fastapi import FastAPI ...@@ -5,6 +5,7 @@ from fastapi import FastAPI
from mapping_provider.api.common import router as version_router from mapping_provider.api.common import router as version_router
from mapping_provider.api.network_graph import router as graph_router from mapping_provider.api.network_graph import router as graph_router
def create_app() -> FastAPI: def create_app() -> FastAPI:
"""Create a FastAPI application.""" """Create a FastAPI application."""
app = FastAPI( app = FastAPI(
...@@ -14,9 +15,10 @@ def create_app() -> FastAPI: ...@@ -14,9 +15,10 @@ def create_app() -> FastAPI:
# Force configuration to be loaded at startup to avoid issues with missing config # Force configuration to be loaded at startup to avoid issues with missing config
from mapping_provider.dependencies import load_config from mapping_provider.dependencies import load_config
_ = load_config() _ = load_config()
app.include_router(version_router) app.include_router(version_router)
app.include_router(graph_router) app.include_router(graph_router)
return app return app
\ No newline at end of file
...@@ -7,5 +7,6 @@ router = APIRouter() ...@@ -7,5 +7,6 @@ router = APIRouter()
@router.get("/network-graph") @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)} return {"routers": load_routers(config), "trunks": load_trunks(config)}
"""The main module that runs the application.""" """The main module that runs the application."""
import uvicorn import uvicorn
from mapping_provider import create_app from mapping_provider import create_app
app = create_app() app = create_app()
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run("mapping_provider.app:app", host="127.0.0.1", port=8000, reload=True) uvicorn.run("mapping_provider.app:app", host="127.0.0.1", port=8000, reload=True)
\ No newline at end of file
"""Configuration file for the Mapping Provider.""" """Configuration file for the Mapping Provider."""
import json import json
from typing import Any, TextIO
import jsonschema import jsonschema
CONFIG_SCHEMA = { CONFIG_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#', "$schema": "http://json-schema.org/draft-07/schema#",
'type': 'object', "type": "object",
'properties': { "properties": {
'aai': { "aai": {
'type': 'object', "type": "object",
'properties': { "properties": {
'discovery_endpoint_url': {'type': 'string'}, "discovery_endpoint_url": {"type": "string"},
'client_id': {'type': 'string'}, "client_id": {"type": "string"},
'secret': {'type': 'string'}, "secret": {"type": "string"},
}, },
'required': ['client_id', 'secret', 'discovery_endpoint_url'], "required": ["client_id", "secret", "discovery_endpoint_url"],
'additionalProperties': False "additionalProperties": False,
}, },
'orchestrator': { "orchestrator": {
'type': 'object', "type": "object",
'properties': { "properties": {
'url': {'type': 'string'}, "url": {"type": "string"},
}, },
'required': ['url'], "required": ["url"],
'additionalProperties': False "additionalProperties": False,
}, },
}, },
'required': ['aai', 'orchestrator'], "required": ["aai", "orchestrator"],
'additionalProperties': False "additionalProperties": False,
} }
def load(f): def load(f: TextIO) -> dict[str, Any]:
"""loads, validates and returns configuration parameters. """loads, validates and returns configuration parameters.
:param f: file-like object that produces the config file :param f: file-like object that produces the config file
:return: :return:
""" """
config = json.loads(f.read()) config: dict[str, Any] = json.loads(f.read())
jsonschema.validate(config, CONFIG_SCHEMA) jsonschema.validate(config, CONFIG_SCHEMA)
return config return config
...@@ -2,14 +2,15 @@ ...@@ -2,14 +2,15 @@
import os import os
from functools import lru_cache from functools import lru_cache
from typing import Annotated from typing import Annotated, Any
from fastapi import Depends from fastapi import Depends
from mapping_provider import config from mapping_provider import config
@lru_cache() @lru_cache
def load_config() -> dict: def load_config() -> dict[str, Any]:
"""Load and cache the application configuration.""" """Load and cache the application configuration."""
config_file = os.environ.get("CONFIG_FILE_NAME", "config.json") config_file = os.environ.get("CONFIG_FILE_NAME", "config.json")
with open(config_file) as f: with open(config_file) as f:
...@@ -17,4 +18,4 @@ def load_config() -> dict: ...@@ -17,4 +18,4 @@ def load_config() -> dict:
# Dependency for injecting config into routes/services # Dependency for injecting config into routes/services
config_dep = Annotated[dict, Depends(load_config)] config_dep = Annotated[dict[str, Any], Depends(load_config)]
\ No newline at end of file
"""External services for the mapping provider.""" """External services for the mapping provider."""
\ No newline at end of file
...@@ -3,6 +3,7 @@ from collections.abc import Callable ...@@ -3,6 +3,7 @@ from collections.abc import Callable
from typing import Any from typing import Any
import requests import requests
from requests import Session
from mapping_provider.dependencies import config_dep from mapping_provider.dependencies import config_dep
...@@ -12,14 +13,15 @@ GRANT_TYPE = "client_credentials" ...@@ -12,14 +13,15 @@ GRANT_TYPE = "client_credentials"
SCOPE = "openid profile email aarc" 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.""" """Fetch the token endpoint URL from discovery document."""
response = session.get(discovery_endpoint_url) response = session.get(discovery_endpoint_url)
response.raise_for_status() 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.""" """Retrieve an access token using client credentials flow."""
aai_config = config["aai"] aai_config = config["aai"]
token_endpoint = get_token_endpoint(aai_config["discovery_endpoint_url"], session) token_endpoint = get_token_endpoint(aai_config["discovery_endpoint_url"], session)
...@@ -33,17 +35,18 @@ def get_token(config: dict, session: requests.Session) -> str: ...@@ -33,17 +35,18 @@ def get_token(config: dict, session: requests.Session) -> str:
}, },
) )
response.raise_for_status() 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.""" """Make a GraphQL request to the orchestrator API."""
api_url = f"{config['orchestrator']['url']}/api/graphql" api_url = f"{config['orchestrator']['url']}/api/graphql"
headers = {"Authorization": f"Bearer {token}"} headers = {"Authorization": f"Bearer {token}"}
response = session.post(api_url, headers=headers, json={"query": query}) response = session.post(api_url, headers=headers, json={"query": query})
response.raise_for_status() response.raise_for_status()
data = response.json() data: dict[str, Any] = response.json()
if "errors" in data: if "errors" in data:
logger.error(f"GraphQL query returned errors: {data['errors']}. Query: {query}") logger.error(f"GraphQL query returned errors: {data['errors']}. Query: {query}")
raise ValueError(f"GraphQL query errors: {data['errors']}") raise ValueError(f"GraphQL query errors: {data['errors']}")
...@@ -51,16 +54,16 @@ def make_request(query: str, token: str, config: dict, session: requests.Session ...@@ -51,16 +54,16 @@ def make_request(query: str, token: str, config: dict, session: requests.Session
return data 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.""" """Extract router FQDNs from productBlockInstances."""
for instance in product_block_instances: for instance in product_block_instances:
for value in instance.get("productBlockInstanceValues", []): for value in instance.get("productBlockInstanceValues", []):
if value.get("field") == "routerFqdn" and value.get("value"): if value.get("field") == "routerFqdn" and value.get("value"):
return value["value"] return str(value["value"])
return None 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.""" """Extract trunks from productBlockInstances."""
fqdns = [] fqdns = []
for instance in product_block_instances: for instance in product_block_instances:
...@@ -75,11 +78,11 @@ def extract_trunk(product_block_instances: list[dict]) -> list[str] | None: ...@@ -75,11 +78,11 @@ def extract_trunk(product_block_instances: list[dict]) -> list[str] | None:
def load_inventory( def load_inventory(
config: dict, config: dict[str, Any],
token: str, token: str,
tag: str, tag: str,
session: requests.Session, session: Session,
extractor: Callable[[list[dict]], Any | None], extractor: Callable[[list[dict[str, Any]]], Any | None],
) -> list[Any]: ) -> list[Any]:
""" """
Generic function to load inventory items based on tag and extractor function. 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]]: ...@@ -143,4 +146,4 @@ def load_trunks(config: config_dep) -> list[list[str]]:
"""Load trunks (edges) from orchestrator.""" """Load trunks (edges) from orchestrator."""
with requests.Session() as session: with requests.Session() as session:
token = get_token(config, session) token = get_token(config, session)
return load_inventory(config, token, tag="IPTRUNK", session=session, extractor=extract_trunk) return load_inventory(config, token, tag="IPTRUNK", session=session, extractor=extract_trunk)
\ No newline at end of file
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
target-version = "py313" target-version = "py312"
select = ["E", "F", "I", "B", "UP", "N"] select = ["E", "F", "I", "B", "UP", "N"]
fixable = ["ALL"] fixable = ["ALL"]
exclude = ["tests", "docs", "build"] exclude = ["tests", "docs", "build"]
...@@ -9,4 +9,14 @@ exclude = ["tests", "docs", "build"] ...@@ -9,4 +9,14 @@ exclude = ["tests", "docs", "build"]
python_version = "3.13" python_version = "3.13"
strict = true strict = true
warn_unused_ignores = true warn_unused_ignores = true
warn_return_any = true warn_return_any = true
\ No newline at end of file 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
...@@ -13,7 +13,9 @@ setup( ...@@ -13,7 +13,9 @@ setup(
python_requires=">=3.10", python_requires=">=3.10",
install_requires=[ install_requires=[
"fastapi", "fastapi",
"uvicorn[standard]" "uvicorn[standard]",
"jsonschema",
"requests",
], ],
long_description=open("README.md", encoding="utf-8").read(), long_description=open("README.md", encoding="utf-8").read(),
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
...@@ -23,4 +25,4 @@ setup( ...@@ -23,4 +25,4 @@ setup(
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Operating System :: OS Independent", "Operating System :: OS Independent",
], ],
) )
\ No newline at end of file
[tox] [tox]
envlist = lint, typecheck, docs envlist = py313
[testenv:lint] [testenv]
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
deps = deps =
sphinx -r requirements.txt
sphinx-rtd-theme commands =
sphinxcontrib-plantuml ruff check --respect-gitignore --preview .
sphinxcontrib-drawio ruff format --respect-gitignore --preview --check .
sphinxcontrib-openapi mypy .
commands = sphinx-build -b html docs/source docs/build
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment