hack-house/.venv/lib/python3.12/site-packages/sanic_ext/extensions/openapi/definitions.py
leetcrypt bb1d662ee1 chore: rename project coven → hack-house ⛧
Rebrand the Rust client crate (coven/ → hh/, package+binary "hack-house"),
README, CLI strings, and branch (coven → hack-house). Gitea repo renamed
cmd-chat → hack-house to match. Crypto/server logic unchanged; selftest +
golden-vector test still green, binary is now `hack-house`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 13:29:14 -07:00

456 lines
12 KiB
Python

"""
Classes defined from the OpenAPI 3.0 specifications.
I.e., the objects described https://swagger.io/docs/specification
"""
from __future__ import annotations
from inspect import isclass
from typing import (
Any,
Literal,
Optional,
Union,
get_type_hints,
)
from sanic.exceptions import SanicException
from sanic_ext.utils.typing import (
contains_annotations,
is_msgspec,
is_pydantic,
)
from .types import Definition, Schema
class Reference(Schema):
def __init__(self, value):
super().__init__(**{"$ref": value})
def guard(self, fields: dict[str, Any]):
return fields
class Contact(Definition):
name: str
url: str
email: str
class License(Definition):
name: str
url: str
def __init__(self, name: str, **kwargs):
super().__init__(name=name, **kwargs)
class Info(Definition):
title: str
description: str
termsOfService: str
contact: Contact
license: License
version: str
def __init__(self, title: str, version: str, **kwargs):
super().__init__(title=title, version=version, **kwargs)
class Example(Definition):
summary: str
description: str
value: Any
externalValue: str
def __init__(self, value: Any = None, **kwargs):
super().__init__(value=value, **kwargs)
@staticmethod
def make(value: Any, **kwargs):
return Example(Schema.make(value), **kwargs)
@staticmethod
def external(value: Any, **kwargs):
return Example(externalValue=value, **kwargs)
class MediaType(Definition):
schema: Schema
example: Any
def __init__(self, schema: Union[Schema, dict[str, Any]], **kwargs):
if isinstance(schema, dict) and contains_annotations(schema):
schema = Schema.make(schema)
super().__init__(schema=schema, **kwargs)
@staticmethod
def make(value: Any):
if isinstance(value, dict):
kwargs = {}
if "schema" in value:
kwargs = {**value}
value = kwargs.pop("schema")
return MediaType(value, **kwargs)
# See https://github.com/sanic-org/sanic-ext/issues/152
# The following lines will automatically inject pydantic models as
# components if that feature is desired. Until that decision is made
# this commented out code will remain.
# elif isclass(value) and is_pydantic(value):
# return MediaType(Component(value))
return MediaType(Schema.make(value))
@staticmethod
def all(content: Any):
media_types = (
content if isinstance(content, dict) else {"*/*": content or {}}
)
return {x: MediaType.make(v) for x, v in media_types.items()}
class Response(Definition):
content: Union[Any, dict[str, Union[Any, MediaType]]]
description: Optional[str]
status: Union[Literal["default"], int]
__nullable__ = None
__ignore__ = ["status"]
def __init__(
self,
content: Optional[Union[Any, dict[str, Union[Any, MediaType]]]] = None,
status: Union[Literal["default"], int] = "default",
description: Optional[str] = None,
**kwargs,
):
super().__init__(
content=content,
status=status,
description=description,
**kwargs,
)
@staticmethod
def make(content, description: Optional[str] = None, **kwargs):
if not description:
description = "Default Response"
return Response(
MediaType.all(content), description=description, **kwargs
)
class RequestBody(Definition):
description: Optional[str]
required: Optional[bool]
content: Union[Any, dict[str, Union[Any, MediaType]]]
__nullable__ = None
def __init__(
self,
content: Union[Any, dict[str, Union[Any, MediaType]]],
required: Optional[bool] = None,
description: Optional[str] = None,
**kwargs,
):
"""Can be initialized with content in one of a few ways:
RequestBody(SomeModel)
RequestBody({"application/json": SomeModel})
RequestBody({"application/json": {"name": str}})
"""
super().__init__(
content=content,
required=required,
description=description,
**kwargs,
)
@staticmethod
def make(content: Any, **kwargs):
return RequestBody(MediaType.all(content), **kwargs)
class ExternalDocumentation(Definition):
url: str
description: str
__nullable__ = None
def __init__(self, url: str, description=None):
super().__init__(url=url, description=description)
@staticmethod
def make(url: str, description: Optional[str] = None):
return ExternalDocumentation(url, description)
class Header(Definition):
name: str
description: str
externalDocs: ExternalDocumentation
def __init__(self, url: str, description=None):
super().__init__(url=url, description=description)
@staticmethod
def make(url: str, description: Optional[str] = None):
return Header(url, description)
class Parameter(Definition):
name: str
schema: Union[type, Schema]
location: str
description: Optional[str]
required: Optional[bool]
deprecated: Optional[bool]
allowEmptyValue: Optional[bool]
__nullable__ = None
def __init__(
self,
name: str,
schema: Union[type, Schema] = str,
location: str = "query",
description: Optional[str] = None,
required: Optional[bool] = None,
deprecated: Optional[bool] = None,
allowEmptyValue: Optional[bool] = None,
**kwargs,
):
super().__init__(
name=name,
schema=schema,
location=location,
description=description,
required=required,
deprecated=deprecated,
allowEmptyValue=allowEmptyValue,
**kwargs,
)
@property
def fields(self):
values = super().fields
if "location" in values:
values["in"] = values.pop("location")
return values
@staticmethod
def make(name: str, schema: type, location: str, **kwargs):
if location == "path" and "required" not in kwargs:
kwargs["required"] = True
return Parameter(name, Schema.make(schema), location, **kwargs)
class Operation(Definition):
tags: list[str]
summary: str
description: str
operationId: str
requestBody: RequestBody
externalDocs: ExternalDocumentation
parameters: list[Parameter]
responses: dict[str, Response]
security: dict[str, list[str]]
callbacks: list[str] # TODO
deprecated: bool
servers: list[dict[str, str]]
class PathItem(Definition):
summary: str
description: str
get: Operation
put: Operation
post: Operation
delete: Operation
options: Operation
head: Operation
patch: Operation
trace: Operation
class Flow(Definition):
authorizationUrl: str
tokenUrl: str
refreshUrl: str
scopes: dict[str, str]
class Flows(Definition):
implicit: Flow
password: Flow
clientCredentials: Flow
authorizationCode: Flow
class SecurityRequirement(Definition):
name: str
value: list[str]
class SecurityScheme(Definition):
type: str
bearerFormat: str
description: str
flows: Flows
location: str
name: str
openIdConnectUrl: str
scheme: str
__nullable__ = None
def __init__(self, type: str, **kwargs):
super().__init__(type=type, **kwargs)
@property
def fields(self):
values = super().fields
if "location" in values:
values["in"] = values.pop("location")
return values
@staticmethod
def make(_type: str, cls: type, **kwargs):
params: dict[str, Any] = getattr(cls, "__dict__", {})
return SecurityScheme(_type, **params, **kwargs)
class ServerVariable(Definition):
default: str
description: str
enum: list[str]
def __init__(self, default: str, **kwargs):
super().__init__(default=default, **kwargs)
class Server(Definition):
url: str
description: str
variables: dict[str, ServerVariable]
__nullable__ = None
def __init__(
self,
url: str,
description: Optional[str] = None,
variables: Optional[dict[str, Any]] = None,
):
super().__init__(
url=url, description=description, variables=variables or {}
)
class Tag(Definition):
name: str
description: str
externalDocs: ExternalDocumentation
def __init__(self, name: str, **kwargs):
super().__init__(name=name, **kwargs)
class Components(Definition):
# This class is not being used in sanic-openapi right now, but the
# definition is kept here to keep in close accordance with the openapi
# spec, in case it is desired to be added later.
schemas: dict[str, Schema]
responses: dict[str, Response]
parameters: dict[str, Parameter]
examples: dict[str, Example]
requestBodies: dict[str, RequestBody]
headers: dict[str, Header]
securitySchemes: dict[str, SecurityScheme]
links: dict[str, Schema] # TODO
callbacks: dict[str, Schema] # TODO
def Component(
obj: Any, *, name: str = "", field: str = "schemas"
) -> Reference:
hints = get_type_hints(Components)
if field not in hints:
raise AttributeError(
f"Unknown field '{field}'. Must be a valid field per OAS3 "
"requirements. See "
"https://swagger.io/specification/#components-object."
)
if not isclass(obj) and not name:
raise SanicException(
f"Components {obj} must be created with a declared name"
)
if not name:
name = obj.__name__
from sanic_ext.extensions.openapi.builders import SpecificationBuilder
spec = SpecificationBuilder()
refval = f"#/components/{field}/{name}"
ref = Reference(refval)
if not spec.has_component(field, name):
prop_info = hints[field]
type_ = prop_info.__args__[1]
if is_msgspec(obj):
import msgspec
_, definitions = msgspec.json.schema_components(
[obj], ref_template="#/components/schemas/{name}"
)
if definitions:
for key, value in definitions.items():
spec.add_component(field, key, value)
elif is_pydantic(obj):
try:
schema = obj.schema
except AttributeError:
schema = obj.__pydantic_model__.schema
component = schema(ref_template="#/components/schemas/{model}")
definitions = component.pop("definitions", None)
if definitions:
for key, value in definitions.items():
spec.add_component(field, key, value)
spec.add_component(field, name, component)
else:
component = (
type_.make(obj) if hasattr(type_, "make") else type_(obj)
)
spec.add_component(field, name, component)
return ref
class OpenAPI(Definition):
openapi: str
info: Info
servers: list[Server]
paths: dict[str, PathItem]
components: Components
security: dict[str, SecurityScheme]
tags: list[Tag]
externalDocs: ExternalDocumentation
def __init__(self, info: Info, paths: dict[str, PathItem], **kwargs):
use = {k: v for k, v in kwargs.items() if v is not None}
super().__init__(openapi="3.0.3", info=info, paths=paths, **use)