We are building a lightweight CLI tool that reads Python source files and produces structured reports covering security risks, complexity hotspots, and maintainability issues. It is useful for teams who want an automated first-pass review before human code review, or for individual developers auditing legacy codebases. Because Oxlo.ai charges a flat rate per API request rather than per token, you can ship entire files to the model without worrying about ballooning costs as input length grows. See https://oxlo.ai/pricing for current plan details.
What you'll need
- Python 3.10 or later
- An Oxlo.ai API key from https://portal.oxlo.ai
- The OpenAI SDK:
pip install openai
Step 1: Bootstrap the client and load source
First we initialize the OpenAI-compatible client pointing at Oxlo.ai and load a target Python file into memory.
from openai import OpenAI
import pathlib
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
def load_source(path: str) -> str:
return pathlib.Path(path).read_text(encoding="utf-8")
if __name__ == "__main__":
code = load_source("app.py")
print(f"Loaded {len(code)} characters")
Step 2: Craft the system prompt
We need the model to return strict JSON so we can parse findings programmatically. Define the prompt as a constant.
SYSTEM_PROMPT = """You are a senior staff engineer performing code review.
Analyze the provided Python code and return a JSON object with exactly these keys:
- "security": list of objects with "line", "severity", and "description"
- "complexity": list of objects with "line", "severity", and "description"
- "maintainability": list of objects with "line", "severity", and "description"
- "summary": string with overall assessment
Severity must be "low", "medium", or "high". If a category has no issues, return an empty list."""
Step 3: Call the model with JSON mode
We pass the file contents as the user message and request JSON output. Oxlo.ai supports JSON mode on compatible models, which forces valid structured responses.
from openai import OpenAI
import json
import pathlib
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
SYSTEM_PROMPT = """You are a senior staff engineer performing code review.
Analyze the provided Python code and return a JSON object with exactly these keys:
- "security": list of objects with "line", "severity", and "description"
- "complexity": list of objects with "line", "severity", and "description"
- "maintainability": list of objects with "line", "severity", and "description"
- "summary": string with overall assessment
Severity must be "low", "medium", or "high". If a category has no issues, return an empty list."""
def load_source(path: str) -> str:
return pathlib.Path(path).read_text(encoding="utf-8")
def analyze_code(file_path: str) -> dict:
source = load_source(file_path)
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Analyze this Python file:\n\n
```{source}```
"},
],
response_format={"type": "json_object"},
)
raw = response.choices[0].message.content
return json.loads(raw)
if __name__ == "__main__":
print(json.dumps(analyze_code("app.py"), indent=2))
Step 4: Augment with local static metrics
To ground the model and reduce hallucinations, we compute basic stats with the ast module and prepend them to the prompt.
from openai import OpenAI
import ast
import json
import pathlib
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
SYSTEM_PROMPT = """You are a senior staff engineer performing code review.
Analyze the provided Python code and return a JSON object with exactly these keys:
- "security": list of objects with "line", "severity", and "description"
- "complexity": list of objects with "line", "severity", and "description"
- "maintainability": list of objects with "line", "severity", and "description"
- "summary": string with overall assessment
Severity must be "low", "medium", or "high". If a category has no issues, return an empty list."""
def load_source(path: str) -> str:
return pathlib.Path(path).read_text(encoding="utf-8")
def get_metrics(source: str) -> str:
tree = ast.parse(source)
funcs = [node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)]
classes = [node for node in ast.walk(tree) if isinstance(node, ast.ClassDef)]
lines = source.splitlines()
return f"File stats: {len(lines)} lines, {len(funcs)} functions, {len(classes)} classes."
def analyze_with_metrics(file_path: str) -> dict:
source = load_source(file_path)
metrics = get_metrics(source)
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"{metrics}\n\nAnalyze this Python file:\n\n
```{source}```
"},
],
response_format={"type": "json_object"},
)
raw = response.choices[0].message.content
return json.loads(raw)
if __name__ == "__main__":
print(json.dumps(analyze_with_metrics("app.py"), indent=2))
Step 5: Batch analysis and reporting
Finally, we loop over a directory, analyze every .py file, and aggregate results into a Markdown report.
from openai import OpenAI
import ast
import glob
import json
import pathlib
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
SYSTEM_PROMPT = """You are a senior staff engineer performing code review.
Analyze the provided Python code and return a JSON object with exactly these keys:
- "security": list of objects with "line", "severity", and "description"
- "complexity": list of objects with "line", "severity", and "description"
- "maintainability": list of objects with "line", "severity", and "description"
- "summary": string with overall assessment
Severity must be "low", "medium", or "high". If a category has no issues, return an empty list."""
def load_source(path: str) -> str:
return pathlib.Path(path).read_text(encoding="utf-8")
def get_metrics(source: str) -> str:
tree = ast.parse(source)
funcs = [node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)]
classes = [node for node in ast.walk(tree) if isinstance(node, ast.ClassDef)]
lines = source.splitlines()
return f"File stats: {len(lines)} lines, {len(funcs)} functions, {len(classes)} classes."
def analyze_with_metrics(file_path: str) -> dict:
source = load_source(file_path)
metrics = get_metrics(source)
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"{metrics}\n\nAnalyze this Python file:\n\n
```{source}```
"},
],
response_format={"type": "json_object"},
)
raw = response.choices[0].message.content
return json.loads(raw)
def generate_report(directory: str, out_file: str = "report.md"):
paths = sorted(glob.glob(f"{directory}/**/*.py", recursive=True))
findings = []
for path in paths:
print(f"Analyzing {path}...")
try:
data = analyze_with_metrics(path)
findings.append({"file": path, "result": data})
except Exception as e:
findings.append({"file": path, "error": str(e)})
with open(out_file, "w", encoding="utf-8") as f:
f.write("# Code Analysis Report\n\n")
for item in findings:
f.write(f"## {item['file']}\n\n")
if "error" in item:
f.write(f"Error: {item['error']}\n\n")
continue
r = item["result"]
f.write(f"**Summary:** {r.get('summary', 'N/A')}\n\n")
for cat in ("security", "complexity", "maintainability"):
issues = r.get(cat, [])
f.write(f"### {cat.title()} ({len(issues)})\n")
if not issues:
f.write("No issues found.\n")
for i in issues:
f.write(f"- Line {i.get('line', '?')}: [{i.get('severity', '?')}] {i.get('description', '')}\n")
f.write("\n")
print(f"Report written to {out_file}")
if __name__ == "__main__":
generate_report("src")
Run it
Create a sample file named app.py with a deliberate bug, then execute the script.
# app.py
import os
def login(password):
if password == "secret123":
return True
return False
def process(data):
eval(data)
return data.upper()
Execute the analyzer:
$ python analyzer.py
Analyzing src/app.py...
Report written to report.md
The generated report.md will look like this:
# Code Analysis Report
## src/app.py
**Summary:** The code contains a hardcoded credential and uses eval on untrusted input. Function complexity is low, but security posture is poor.
### Security (2)
- Line 4: [high] Hardcoded password comparison with a weak literal string.
- Line 10: [high] Use of eval() on user-supplied data enables arbitrary code execution.
### Complexity (0)
No issues found.
### Maintainability (1)
- Line 10: [medium] eval() makes debugging and static analysis difficult; refactor to explicit parsing.
Next steps
Wire the analyzer into a pre-commit hook so every pull request gets an automatic first-pass review before human eyes see it. If you need stronger reasoning on complex enterprise codebases, swap the model to kimi-k2.6 or deepseek-v3.2 on Oxlo.ai, both of which handle long context and agentic coding tasks well.
Top comments (0)