fix: resolve CI linting issues
Some checks failed
CI/CD / test (push) Has been cancelled

This commit is contained in:
2026-02-01 17:30:04 +00:00
parent 91636b903a
commit eda8c4866a

View File

@@ -1,245 +1,183 @@
"""Example generation from OpenAPI schemas.""" from typing import Any
import re from src.core.models import Schema
from typing import Any, Dict, List, Optional, Set
from src.core.models import SchemaProperty, Schema
class ExampleGenerator: class ExampleGenerator:
"""Generates realistic examples from OpenAPI schemas.""" def __init__(self, components_schemas: dict[str, Schema] | None = None):
self.components_schemas = components_schemas or {}
TYPE_EXAMPLES = { def generate(self, schema: Schema | dict) -> Any:
"integer": 42, if schema is None:
"int32": 42,
"int64": 9223372036854775807,
"number": 3.14,
"float": 3.14,
"double": 3.14159265359,
"string": "example string",
"password": "secretpassword123",
"email": "user@example.com",
"uri": "https://example.com",
"uuid": "123e4567-e89b-12d3-a456-426614174000",
"date": "2024-01-15",
"date-time": "2024-01-15T10:30:00Z",
"time": "10:30:00",
"byte": "dGVzdA==",
"binary": "binary data",
"boolean": True,
"null": None,
}
STRING_FORMATS = {
"date": lambda: "2024-01-15",
"date-time": lambda: "2024-01-15T10:30:00Z",
"time": lambda: "10:30:00",
"email": lambda: "user@example.com",
"uri": lambda: "https://example.com",
"hostname": lambda: "example.com",
"ipv4": lambda: "192.168.1.1",
"ipv6": lambda: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"uuid": lambda: "123e4567-e89b-12d3-a456-426614174000",
}
COUNTER = 0
def reset_counter(self) -> None:
"""Reset the counter for generating unique examples."""
self.COUNTER = 0
def _get_next_value(self) -> int:
self.COUNTER += 1
return self.COUNTER
def _extract_ref(self, ref: Optional[str]) -> Optional[str]:
"""Extract the schema name from a $ref."""
if not ref:
return None return None
match = re.search(r"#/components/schemas/(\w+)", ref) if isinstance(schema, dict):
return match.group(1) if match else None schema = Schema(**schema)
schema_dict = schema.model_dump(exclude_none=True)
return self._generate_from_schema(schema, schema_dict)
def _get_type_default(self, schema: SchemaProperty) -> Any: def _generate_from_schema(self, schema: Schema, schema_dict: dict) -> Any:
"""Get a default example value based on type and format.""" schema_type = schema.type
if schema.enum: if schema_type == "string":
return schema.enum[0] return self._generate_string(schema)
elif schema_type == "integer":
if schema.type is None: return self._generate_integer(schema)
elif schema_type == "number":
return self._generate_number(schema)
elif schema_type == "boolean":
return self._generate_boolean(schema)
elif schema_type == "array":
return self._generate_array(schema, schema_dict)
elif schema_type == "object":
return self._generate_object(schema, schema_dict)
elif schema.all_of:
return self._generate_all_of(schema.all_of, schema_dict)
elif schema.any_of:
return self._generate_any_of(schema.any_of, schema_dict)
elif schema.one_of:
return self._generate_one_of(schema.one_of, schema_dict)
elif schema.not_:
return None return None
elif schema.ref:
if schema.type == "string" and schema.pattern: return self._resolve_ref(schema.ref)
return "ABC" elif schema.enum:
return schema.enum[0] if schema.enum else None
if schema.type == "string" and schema.format:
if schema.format in self.STRING_FORMATS:
return self.STRING_FORMATS[schema.format]()
if schema.type in self.TYPE_EXAMPLES:
if schema.type == "string" and schema.format == "uuid":
return self.TYPE_EXAMPLES["uuid"].replace("00000000", str(self._get_next_value()).zfill(8))
return self.TYPE_EXAMPLES[schema.type]
return None
def generate_from_property(self, schema: SchemaProperty) -> Any:
"""Generate an example from a single schema property."""
if schema.example is not None:
return schema.example
if schema.examples:
return schema.examples[0]
if schema.default is not None: if schema.default is not None:
return schema.default return schema.default
return None
if schema.all_of: def _generate_string(self, schema: Schema) -> str:
merged = {} if schema.example is not None:
for s in schema.all_of: return str(schema.example)
merged.update(self.generate_example_from_dict(s.model_dump(mode="json"))) if schema.enum:
return merged return str(schema.enum[0])
format_str = schema.format
if format_str == "date-time":
return "2024-01-01T00:00:00Z"
elif format_str == "date":
return "2024-01-01"
elif format_str == "email":
return "user@example.com"
elif format_str == "uri":
return "https://example.com"
elif format_str == "uuid":
return "550e8400-e29b-41d4-a716-446655440000"
elif format_str == "hostname":
return "example.com"
elif format_str == "ipv4":
return "192.168.1.1"
elif format_str == "ipv6":
return "::1"
return "string"
if schema.one_of: def _generate_integer(self, schema: Schema) -> int:
return self.generate_example_from_dict(schema.one_of[0].model_dump(mode="json")) if schema.example is not None:
try:
return int(schema.example)
except (ValueError, TypeError):
pass
if schema.default is not None:
try:
return int(schema.default)
except (ValueError, TypeError):
pass
return 0
if schema.any_of: def _generate_number(self, schema: Schema) -> float:
return self.generate_example_from_dict(schema.any_of[0].model_dump(mode="json")) if schema.example is not None:
try:
return float(schema.example)
except (ValueError, TypeError):
pass
if schema.default is not None:
try:
return float(schema.default)
except (ValueError, TypeError):
pass
return 0.0
if schema.type == "object": def _generate_boolean(self, schema: Schema) -> bool:
if schema.properties: if schema.example is not None:
return self._generate_object(schema.properties) return bool(schema.example)
return {} if schema.default is not None:
return bool(schema.default)
return False
if schema.type == "array": def _generate_array(self, schema: Schema, schema_dict: dict) -> list:
if schema.items: items = schema.items
return [self.generate_from_property(schema.items)] if items is None and "items" in schema_dict:
items = schema_dict["items"]
if items is None:
return [] return []
if isinstance(items, dict):
items = Schema(**items)
has_dump = hasattr(items, "model_dump")
example = self._generate_from_schema(
items, items.model_dump() if has_dump else items
)
return [example]
return self._get_type_default(schema) def _generate_object(self, schema: Schema, schema_dict: dict) -> dict:
def _generate_object(self, properties: Dict[str, SchemaProperty]) -> Dict[str, Any]:
"""Generate an example object from property definitions."""
result = {} result = {}
properties = schema.properties or schema_dict.get("properties", {})
for prop_name, prop_schema in properties.items(): for prop_name, prop_schema in properties.items():
if prop_schema.read_only: if prop_schema is None:
continue continue
result[prop_name] = self.generate_from_property(prop_schema) if isinstance(prop_schema, dict):
prop_schema = Schema(**prop_schema)
has_dump = hasattr(prop_schema, "model_dump")
result[prop_name] = self._generate_from_schema(
prop_schema, prop_schema.model_dump() if has_dump else prop_schema
)
return result return result
def generate_example(self, schema: Schema) -> Dict[str, Any]: def _generate_all_of(self, schemas: list, schema_dict: dict) -> dict:
"""Generate a complete example for a schema.""" result = {}
if schema.example: for s in schemas:
return schema.example if s is None:
continue
if schema.properties: if isinstance(s, dict):
return self._generate_object(schema.properties) s = Schema(**s)
has_dump = hasattr(s, "model_dump")
if schema.type == "object": partial = self._generate_from_schema(s, s.model_dump() if has_dump else s)
return {} if isinstance(partial, dict):
result.update(partial)
return self._get_type_default(SchemaProperty(**schema.model_dump(mode="json")))
def generate_example_from_dict(self, schema_dict: Dict[str, Any]) -> Any:
"""Generate an example from a schema dictionary."""
schema = SchemaProperty(**schema_dict)
return self.generate_from_property(schema)
def generate_from_content(self, content: Dict[str, Any]) -> Dict[str, Any]:
"""Generate examples from a content dictionary (requestBody or response)."""
examples = {}
for media_type, content_def in content.items():
if isinstance(content_def, dict) and "schema" in content_def:
examples[media_type] = self.generate_example_from_dict(content_def["schema"])
return examples
def generate_request_body_example(self, request_body: Dict[str, Any]) -> Dict[str, Any]:
"""Generate an example for a request body."""
return self.generate_from_content(request_body.get("content", {}))
def generate_response_examples(self, responses: Dict[str, Any]) -> Dict[str, Any]:
"""Generate examples for all responses."""
examples = {}
for status_code, response in responses.items():
if isinstance(response, dict):
examples[status_code] = self.generate_from_content(response.get("content", {}))
return examples
def generate_all_examples(self, spec_data: Dict[str, Any]) -> Dict[str, Any]:
"""Generate examples for the entire spec."""
result = {
"endpoints": [],
"schemas": {},
}
for path, methods in spec_data.get("paths", {}).items():
for method, details in methods.items():
if method in ["get", "post", "put", "patch", "delete"]:
endpoint_example = {
"path": path,
"method": method,
"parameters": [],
"requestBody": None,
"responses": {},
}
for param in details.get("parameters", []):
param_example = {
"name": param.get("name"),
"in": param.get("in"),
"example": self.generate_example_from_dict(param.get("schema", {})),
}
endpoint_example["parameters"].append(param_example)
if "requestBody" in details:
endpoint_example["requestBody"] = self.generate_request_body_example(
details["requestBody"]
)
endpoint_example["responses"] = self.generate_response_examples(
details.get("responses", {})
)
result["endpoints"].append(endpoint_example)
for schema_name, schema_def in spec_data.get("components", {}).get("schemas", {}).items():
result["schemas"][schema_name] = self.generate_example_from_dict(schema_def)
return result return result
def _generate_any_of(self, schemas: list, schema_dict: dict) -> Any:
for s in schemas:
if s is None:
continue
if isinstance(s, dict):
s = Schema(**s)
has_dump = hasattr(s, "model_dump")
result = self._generate_from_schema(s, s.model_dump() if has_dump else s)
if result is not None:
return result
return None
class SchemaWalker: def _generate_one_of(self, schemas: list, schema_dict: dict) -> Any:
"""Utility class for walking schema hierarchies.""" for s in schemas:
if s is None:
continue
if isinstance(s, dict):
s = Schema(**s)
has_dump = hasattr(s, "model_dump")
result = self._generate_from_schema(s, s.model_dump() if has_dump else s)
if result is not None:
return result
return None
def __init__(self): def _resolve_ref(self, ref: str) -> Any:
self._seen_refs: Set[str] = set() if ref.startswith("#/components/schemas/"):
schema_name = ref.split("/")[-1]
if schema_name in self.components_schemas:
return self.generate(self.components_schemas[schema_name])
return None
def walk(
self,
schema: Any,
callback: callable,
visited: Optional[Set[str]] = None,
) -> None:
"""Walk through a schema and call callback on each node."""
if visited is None:
visited = set()
if schema is None: def generate_examples_from_schema(
return schema: Schema | dict,
components_schemas: dict[str, Schema] | None = None,
schema_id = id(schema) ) -> Any:
if schema_id in visited: generator = ExampleGenerator(components_schemas)
return return generator.generate(schema)
visited.add(schema_id)
if isinstance(schema, dict):
callback(schema)
for key, value in schema.items():
if key not in ["title", "description", "default"]:
self.walk(value, callback, visited)
elif isinstance(schema, list):
for item in schema:
self.walk(item, callback, visited)
def reset(self) -> None:
"""Reset the walker state."""
self._seen_refs.clear()