models.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. from enum import Enum
  2. from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Type, Union
  3. from fastapi._compat import (
  4. PYDANTIC_V2,
  5. CoreSchema,
  6. GetJsonSchemaHandler,
  7. JsonSchemaValue,
  8. _model_rebuild,
  9. with_info_plain_validator_function,
  10. )
  11. from fastapi.logger import logger
  12. from pydantic import AnyUrl, BaseModel, Field
  13. from typing_extensions import Annotated, Literal, TypedDict
  14. from typing_extensions import deprecated as typing_deprecated
  15. try:
  16. import email_validator
  17. assert email_validator # make autoflake ignore the unused import
  18. from pydantic import EmailStr
  19. except ImportError: # pragma: no cover
  20. class EmailStr(str): # type: ignore
  21. @classmethod
  22. def __get_validators__(cls) -> Iterable[Callable[..., Any]]:
  23. yield cls.validate
  24. @classmethod
  25. def validate(cls, v: Any) -> str:
  26. logger.warning(
  27. "email-validator not installed, email fields will be treated as str.\n"
  28. "To install, run: pip install email-validator"
  29. )
  30. return str(v)
  31. @classmethod
  32. def _validate(cls, __input_value: Any, _: Any) -> str:
  33. logger.warning(
  34. "email-validator not installed, email fields will be treated as str.\n"
  35. "To install, run: pip install email-validator"
  36. )
  37. return str(__input_value)
  38. @classmethod
  39. def __get_pydantic_json_schema__(
  40. cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler
  41. ) -> JsonSchemaValue:
  42. return {"type": "string", "format": "email"}
  43. @classmethod
  44. def __get_pydantic_core_schema__(
  45. cls, source: Type[Any], handler: Callable[[Any], CoreSchema]
  46. ) -> CoreSchema:
  47. return with_info_plain_validator_function(cls._validate)
  48. class Contact(BaseModel):
  49. name: Optional[str] = None
  50. url: Optional[AnyUrl] = None
  51. email: Optional[EmailStr] = None
  52. if PYDANTIC_V2:
  53. model_config = {"extra": "allow"}
  54. else:
  55. class Config:
  56. extra = "allow"
  57. class License(BaseModel):
  58. name: str
  59. identifier: Optional[str] = None
  60. url: Optional[AnyUrl] = None
  61. if PYDANTIC_V2:
  62. model_config = {"extra": "allow"}
  63. else:
  64. class Config:
  65. extra = "allow"
  66. class Info(BaseModel):
  67. title: str
  68. summary: Optional[str] = None
  69. description: Optional[str] = None
  70. termsOfService: Optional[str] = None
  71. contact: Optional[Contact] = None
  72. license: Optional[License] = None
  73. version: str
  74. if PYDANTIC_V2:
  75. model_config = {"extra": "allow"}
  76. else:
  77. class Config:
  78. extra = "allow"
  79. class ServerVariable(BaseModel):
  80. enum: Annotated[Optional[List[str]], Field(min_length=1)] = None
  81. default: str
  82. description: Optional[str] = None
  83. if PYDANTIC_V2:
  84. model_config = {"extra": "allow"}
  85. else:
  86. class Config:
  87. extra = "allow"
  88. class Server(BaseModel):
  89. url: Union[AnyUrl, str]
  90. description: Optional[str] = None
  91. variables: Optional[Dict[str, ServerVariable]] = None
  92. if PYDANTIC_V2:
  93. model_config = {"extra": "allow"}
  94. else:
  95. class Config:
  96. extra = "allow"
  97. class Reference(BaseModel):
  98. ref: str = Field(alias="$ref")
  99. class Discriminator(BaseModel):
  100. propertyName: str
  101. mapping: Optional[Dict[str, str]] = None
  102. class XML(BaseModel):
  103. name: Optional[str] = None
  104. namespace: Optional[str] = None
  105. prefix: Optional[str] = None
  106. attribute: Optional[bool] = None
  107. wrapped: Optional[bool] = None
  108. if PYDANTIC_V2:
  109. model_config = {"extra": "allow"}
  110. else:
  111. class Config:
  112. extra = "allow"
  113. class ExternalDocumentation(BaseModel):
  114. description: Optional[str] = None
  115. url: AnyUrl
  116. if PYDANTIC_V2:
  117. model_config = {"extra": "allow"}
  118. else:
  119. class Config:
  120. extra = "allow"
  121. class Schema(BaseModel):
  122. # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu
  123. # Core Vocabulary
  124. schema_: Optional[str] = Field(default=None, alias="$schema")
  125. vocabulary: Optional[str] = Field(default=None, alias="$vocabulary")
  126. id: Optional[str] = Field(default=None, alias="$id")
  127. anchor: Optional[str] = Field(default=None, alias="$anchor")
  128. dynamicAnchor: Optional[str] = Field(default=None, alias="$dynamicAnchor")
  129. ref: Optional[str] = Field(default=None, alias="$ref")
  130. dynamicRef: Optional[str] = Field(default=None, alias="$dynamicRef")
  131. defs: Optional[Dict[str, "SchemaOrBool"]] = Field(default=None, alias="$defs")
  132. comment: Optional[str] = Field(default=None, alias="$comment")
  133. # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s
  134. # A Vocabulary for Applying Subschemas
  135. allOf: Optional[List["SchemaOrBool"]] = None
  136. anyOf: Optional[List["SchemaOrBool"]] = None
  137. oneOf: Optional[List["SchemaOrBool"]] = None
  138. not_: Optional["SchemaOrBool"] = Field(default=None, alias="not")
  139. if_: Optional["SchemaOrBool"] = Field(default=None, alias="if")
  140. then: Optional["SchemaOrBool"] = None
  141. else_: Optional["SchemaOrBool"] = Field(default=None, alias="else")
  142. dependentSchemas: Optional[Dict[str, "SchemaOrBool"]] = None
  143. prefixItems: Optional[List["SchemaOrBool"]] = None
  144. # TODO: uncomment and remove below when deprecating Pydantic v1
  145. # It generales a list of schemas for tuples, before prefixItems was available
  146. # items: Optional["SchemaOrBool"] = None
  147. items: Optional[Union["SchemaOrBool", List["SchemaOrBool"]]] = None
  148. contains: Optional["SchemaOrBool"] = None
  149. properties: Optional[Dict[str, "SchemaOrBool"]] = None
  150. patternProperties: Optional[Dict[str, "SchemaOrBool"]] = None
  151. additionalProperties: Optional["SchemaOrBool"] = None
  152. propertyNames: Optional["SchemaOrBool"] = None
  153. unevaluatedItems: Optional["SchemaOrBool"] = None
  154. unevaluatedProperties: Optional["SchemaOrBool"] = None
  155. # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural
  156. # A Vocabulary for Structural Validation
  157. type: Optional[str] = None
  158. enum: Optional[List[Any]] = None
  159. const: Optional[Any] = None
  160. multipleOf: Optional[float] = Field(default=None, gt=0)
  161. maximum: Optional[float] = None
  162. exclusiveMaximum: Optional[float] = None
  163. minimum: Optional[float] = None
  164. exclusiveMinimum: Optional[float] = None
  165. maxLength: Optional[int] = Field(default=None, ge=0)
  166. minLength: Optional[int] = Field(default=None, ge=0)
  167. pattern: Optional[str] = None
  168. maxItems: Optional[int] = Field(default=None, ge=0)
  169. minItems: Optional[int] = Field(default=None, ge=0)
  170. uniqueItems: Optional[bool] = None
  171. maxContains: Optional[int] = Field(default=None, ge=0)
  172. minContains: Optional[int] = Field(default=None, ge=0)
  173. maxProperties: Optional[int] = Field(default=None, ge=0)
  174. minProperties: Optional[int] = Field(default=None, ge=0)
  175. required: Optional[List[str]] = None
  176. dependentRequired: Optional[Dict[str, Set[str]]] = None
  177. # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c
  178. # Vocabularies for Semantic Content With "format"
  179. format: Optional[str] = None
  180. # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten
  181. # A Vocabulary for the Contents of String-Encoded Data
  182. contentEncoding: Optional[str] = None
  183. contentMediaType: Optional[str] = None
  184. contentSchema: Optional["SchemaOrBool"] = None
  185. # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta
  186. # A Vocabulary for Basic Meta-Data Annotations
  187. title: Optional[str] = None
  188. description: Optional[str] = None
  189. default: Optional[Any] = None
  190. deprecated: Optional[bool] = None
  191. readOnly: Optional[bool] = None
  192. writeOnly: Optional[bool] = None
  193. examples: Optional[List[Any]] = None
  194. # Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object
  195. # Schema Object
  196. discriminator: Optional[Discriminator] = None
  197. xml: Optional[XML] = None
  198. externalDocs: Optional[ExternalDocumentation] = None
  199. example: Annotated[
  200. Optional[Any],
  201. typing_deprecated(
  202. "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
  203. "although still supported. Use examples instead."
  204. ),
  205. ] = None
  206. if PYDANTIC_V2:
  207. model_config = {"extra": "allow"}
  208. else:
  209. class Config:
  210. extra = "allow"
  211. # Ref: https://json-schema.org/draft/2020-12/json-schema-core.html#name-json-schema-documents
  212. # A JSON Schema MUST be an object or a boolean.
  213. SchemaOrBool = Union[Schema, bool]
  214. class Example(TypedDict, total=False):
  215. summary: Optional[str]
  216. description: Optional[str]
  217. value: Optional[Any]
  218. externalValue: Optional[AnyUrl]
  219. if PYDANTIC_V2: # type: ignore [misc]
  220. __pydantic_config__ = {"extra": "allow"}
  221. else:
  222. class Config:
  223. extra = "allow"
  224. class ParameterInType(Enum):
  225. query = "query"
  226. header = "header"
  227. path = "path"
  228. cookie = "cookie"
  229. class Encoding(BaseModel):
  230. contentType: Optional[str] = None
  231. headers: Optional[Dict[str, Union["Header", Reference]]] = None
  232. style: Optional[str] = None
  233. explode: Optional[bool] = None
  234. allowReserved: Optional[bool] = None
  235. if PYDANTIC_V2:
  236. model_config = {"extra": "allow"}
  237. else:
  238. class Config:
  239. extra = "allow"
  240. class MediaType(BaseModel):
  241. schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema")
  242. example: Optional[Any] = None
  243. examples: Optional[Dict[str, Union[Example, Reference]]] = None
  244. encoding: Optional[Dict[str, Encoding]] = None
  245. if PYDANTIC_V2:
  246. model_config = {"extra": "allow"}
  247. else:
  248. class Config:
  249. extra = "allow"
  250. class ParameterBase(BaseModel):
  251. description: Optional[str] = None
  252. required: Optional[bool] = None
  253. deprecated: Optional[bool] = None
  254. # Serialization rules for simple scenarios
  255. style: Optional[str] = None
  256. explode: Optional[bool] = None
  257. allowReserved: Optional[bool] = None
  258. schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema")
  259. example: Optional[Any] = None
  260. examples: Optional[Dict[str, Union[Example, Reference]]] = None
  261. # Serialization rules for more complex scenarios
  262. content: Optional[Dict[str, MediaType]] = None
  263. if PYDANTIC_V2:
  264. model_config = {"extra": "allow"}
  265. else:
  266. class Config:
  267. extra = "allow"
  268. class Parameter(ParameterBase):
  269. name: str
  270. in_: ParameterInType = Field(alias="in")
  271. class Header(ParameterBase):
  272. pass
  273. class RequestBody(BaseModel):
  274. description: Optional[str] = None
  275. content: Dict[str, MediaType]
  276. required: Optional[bool] = None
  277. if PYDANTIC_V2:
  278. model_config = {"extra": "allow"}
  279. else:
  280. class Config:
  281. extra = "allow"
  282. class Link(BaseModel):
  283. operationRef: Optional[str] = None
  284. operationId: Optional[str] = None
  285. parameters: Optional[Dict[str, Union[Any, str]]] = None
  286. requestBody: Optional[Union[Any, str]] = None
  287. description: Optional[str] = None
  288. server: Optional[Server] = None
  289. if PYDANTIC_V2:
  290. model_config = {"extra": "allow"}
  291. else:
  292. class Config:
  293. extra = "allow"
  294. class Response(BaseModel):
  295. description: str
  296. headers: Optional[Dict[str, Union[Header, Reference]]] = None
  297. content: Optional[Dict[str, MediaType]] = None
  298. links: Optional[Dict[str, Union[Link, Reference]]] = None
  299. if PYDANTIC_V2:
  300. model_config = {"extra": "allow"}
  301. else:
  302. class Config:
  303. extra = "allow"
  304. class Operation(BaseModel):
  305. tags: Optional[List[str]] = None
  306. summary: Optional[str] = None
  307. description: Optional[str] = None
  308. externalDocs: Optional[ExternalDocumentation] = None
  309. operationId: Optional[str] = None
  310. parameters: Optional[List[Union[Parameter, Reference]]] = None
  311. requestBody: Optional[Union[RequestBody, Reference]] = None
  312. # Using Any for Specification Extensions
  313. responses: Optional[Dict[str, Union[Response, Any]]] = None
  314. callbacks: Optional[Dict[str, Union[Dict[str, "PathItem"], Reference]]] = None
  315. deprecated: Optional[bool] = None
  316. security: Optional[List[Dict[str, List[str]]]] = None
  317. servers: Optional[List[Server]] = None
  318. if PYDANTIC_V2:
  319. model_config = {"extra": "allow"}
  320. else:
  321. class Config:
  322. extra = "allow"
  323. class PathItem(BaseModel):
  324. ref: Optional[str] = Field(default=None, alias="$ref")
  325. summary: Optional[str] = None
  326. description: Optional[str] = None
  327. get: Optional[Operation] = None
  328. put: Optional[Operation] = None
  329. post: Optional[Operation] = None
  330. delete: Optional[Operation] = None
  331. options: Optional[Operation] = None
  332. head: Optional[Operation] = None
  333. patch: Optional[Operation] = None
  334. trace: Optional[Operation] = None
  335. servers: Optional[List[Server]] = None
  336. parameters: Optional[List[Union[Parameter, Reference]]] = None
  337. if PYDANTIC_V2:
  338. model_config = {"extra": "allow"}
  339. else:
  340. class Config:
  341. extra = "allow"
  342. class SecuritySchemeType(Enum):
  343. apiKey = "apiKey"
  344. http = "http"
  345. oauth2 = "oauth2"
  346. openIdConnect = "openIdConnect"
  347. class SecurityBase(BaseModel):
  348. type_: SecuritySchemeType = Field(alias="type")
  349. description: Optional[str] = None
  350. if PYDANTIC_V2:
  351. model_config = {"extra": "allow"}
  352. else:
  353. class Config:
  354. extra = "allow"
  355. class APIKeyIn(Enum):
  356. query = "query"
  357. header = "header"
  358. cookie = "cookie"
  359. class APIKey(SecurityBase):
  360. type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias="type")
  361. in_: APIKeyIn = Field(alias="in")
  362. name: str
  363. class HTTPBase(SecurityBase):
  364. type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias="type")
  365. scheme: str
  366. class HTTPBearer(HTTPBase):
  367. scheme: Literal["bearer"] = "bearer"
  368. bearerFormat: Optional[str] = None
  369. class OAuthFlow(BaseModel):
  370. refreshUrl: Optional[str] = None
  371. scopes: Dict[str, str] = {}
  372. if PYDANTIC_V2:
  373. model_config = {"extra": "allow"}
  374. else:
  375. class Config:
  376. extra = "allow"
  377. class OAuthFlowImplicit(OAuthFlow):
  378. authorizationUrl: str
  379. class OAuthFlowPassword(OAuthFlow):
  380. tokenUrl: str
  381. class OAuthFlowClientCredentials(OAuthFlow):
  382. tokenUrl: str
  383. class OAuthFlowAuthorizationCode(OAuthFlow):
  384. authorizationUrl: str
  385. tokenUrl: str
  386. class OAuthFlows(BaseModel):
  387. implicit: Optional[OAuthFlowImplicit] = None
  388. password: Optional[OAuthFlowPassword] = None
  389. clientCredentials: Optional[OAuthFlowClientCredentials] = None
  390. authorizationCode: Optional[OAuthFlowAuthorizationCode] = None
  391. if PYDANTIC_V2:
  392. model_config = {"extra": "allow"}
  393. else:
  394. class Config:
  395. extra = "allow"
  396. class OAuth2(SecurityBase):
  397. type_: SecuritySchemeType = Field(default=SecuritySchemeType.oauth2, alias="type")
  398. flows: OAuthFlows
  399. class OpenIdConnect(SecurityBase):
  400. type_: SecuritySchemeType = Field(
  401. default=SecuritySchemeType.openIdConnect, alias="type"
  402. )
  403. openIdConnectUrl: str
  404. SecurityScheme = Union[APIKey, HTTPBase, OAuth2, OpenIdConnect, HTTPBearer]
  405. class Components(BaseModel):
  406. schemas: Optional[Dict[str, Union[Schema, Reference]]] = None
  407. responses: Optional[Dict[str, Union[Response, Reference]]] = None
  408. parameters: Optional[Dict[str, Union[Parameter, Reference]]] = None
  409. examples: Optional[Dict[str, Union[Example, Reference]]] = None
  410. requestBodies: Optional[Dict[str, Union[RequestBody, Reference]]] = None
  411. headers: Optional[Dict[str, Union[Header, Reference]]] = None
  412. securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None
  413. links: Optional[Dict[str, Union[Link, Reference]]] = None
  414. # Using Any for Specification Extensions
  415. callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference, Any]]] = None
  416. pathItems: Optional[Dict[str, Union[PathItem, Reference]]] = None
  417. if PYDANTIC_V2:
  418. model_config = {"extra": "allow"}
  419. else:
  420. class Config:
  421. extra = "allow"
  422. class Tag(BaseModel):
  423. name: str
  424. description: Optional[str] = None
  425. externalDocs: Optional[ExternalDocumentation] = None
  426. if PYDANTIC_V2:
  427. model_config = {"extra": "allow"}
  428. else:
  429. class Config:
  430. extra = "allow"
  431. class OpenAPI(BaseModel):
  432. openapi: str
  433. info: Info
  434. jsonSchemaDialect: Optional[str] = None
  435. servers: Optional[List[Server]] = None
  436. # Using Any for Specification Extensions
  437. paths: Optional[Dict[str, Union[PathItem, Any]]] = None
  438. webhooks: Optional[Dict[str, Union[PathItem, Reference]]] = None
  439. components: Optional[Components] = None
  440. security: Optional[List[Dict[str, List[str]]]] = None
  441. tags: Optional[List[Tag]] = None
  442. externalDocs: Optional[ExternalDocumentation] = None
  443. if PYDANTIC_V2:
  444. model_config = {"extra": "allow"}
  445. else:
  446. class Config:
  447. extra = "allow"
  448. _model_rebuild(Schema)
  449. _model_rebuild(Operation)
  450. _model_rebuild(Encoding)