Coverage for src/lexigram/admin/openapi/resource_converter.py: 100%

34 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 14:56 +0800

1from __future__ import annotations 

2 

3from typing import TYPE_CHECKING, Any 

4 

5from lexigram.admin.openapi.field_converter import field_to_openapi_property 

6 

7if TYPE_CHECKING: 

8 from lexigram.admin.schema.base import SchemaField 

9 

10 

11def resource_to_schema( 

12 name: str, 

13 fields: list[SchemaField], 

14) -> dict[str, Any]: 

15 """Convert a resource's fields to an OpenAPI Schema Object. 

16 

17 Args: 

18 name: Resource name (used as the schema title). 

19 fields: List of SchemaField instances. 

20 

21 Returns: 

22 An OpenAPI Schema Object dict with ``type: object``, ``properties``, 

23 and ``required`` fields. 

24 """ 

25 properties: dict[str, Any] = {} 

26 required: list[str] = [] 

27 for field in fields: 

28 prop = field_to_openapi_property(field) 

29 properties[field.name] = prop 

30 if field.required: 

31 required.append(field.name) 

32 

33 schema: dict[str, Any] = { 

34 "type": "object", 

35 "properties": properties, 

36 "title": name, 

37 } 

38 if required: 

39 schema["required"] = required 

40 return schema 

41 

42 

43def resources_to_openapi_spec( 

44 resources: dict[str, Any], 

45 *, 

46 title: str = "Admin API", 

47 version: str = "1.0.0", 

48) -> dict[str, Any]: 

49 """Convert admin resources to a full OpenAPI 3.0.3 specification. 

50 

51 Args: 

52 resources: A ``{name: resource_instance}`` dict, where each resource 

53 has a ``fields`` attribute containing ``SchemaField`` instances. 

54 title: OpenAPI info title. 

55 version: OpenAPI spec version. 

56 

57 Returns: 

58 A complete OpenAPI 3.0.3 spec dict with paths, schemas, and tags. 

59 """ 

60 schemas: dict[str, Any] = {} 

61 paths: dict[str, Any] = {} 

62 tags: list[dict[str, str]] = [] 

63 

64 for resource_name, resource in resources.items(): 

65 fields: list[SchemaField] = getattr(resource, "fields", []) or [] 

66 label: str = getattr(resource, "label", resource_name) 

67 

68 schema = resource_to_schema(resource_name, fields) 

69 schema_name = f"{resource_name}.Resource" 

70 schemas[schema_name] = schema 

71 

72 list_schema_name = f"{resource_name}.ListResponse" 

73 schemas[list_schema_name] = { 

74 "type": "object", 

75 "properties": { 

76 "data": { 

77 "type": "array", 

78 "items": {"$ref": f"#/components/schemas/{schema_name}"}, 

79 }, 

80 "total": {"type": "integer"}, 

81 }, 

82 } 

83 

84 tags.append({"name": resource_name, "description": label}) 

85 

86 prefix = f"/api/{resource_name}" 

87 

88 paths[prefix] = { 

89 "get": { 

90 "tags": [resource_name], 

91 "summary": f"List {label}", 

92 "operationId": f"list{resource_name.title()}", 

93 "parameters": [ 

94 { 

95 "name": "page", 

96 "in": "query", 

97 "schema": {"type": "integer", "default": 1}, 

98 }, 

99 { 

100 "name": "per_page", 

101 "in": "query", 

102 "schema": {"type": "integer", "default": 15}, 

103 }, 

104 ], 

105 "responses": { 

106 "200": { 

107 "description": "Successful response", 

108 "content": { 

109 "application/json": { 

110 "schema": { 

111 "$ref": f"#/components/schemas/{list_schema_name}" 

112 }, 

113 }, 

114 }, 

115 }, 

116 }, 

117 }, 

118 "post": { 

119 "tags": [resource_name], 

120 "summary": f"Create {label}", 

121 "operationId": f"create{resource_name.title()}", 

122 "requestBody": { 

123 "required": True, 

124 "content": { 

125 "application/json": { 

126 "schema": {"$ref": f"#/components/schemas/{schema_name}"}, 

127 }, 

128 }, 

129 }, 

130 "responses": { 

131 "201": { 

132 "description": "Created", 

133 "content": { 

134 "application/json": { 

135 "schema": { 

136 "$ref": f"#/components/schemas/{schema_name}" 

137 }, 

138 }, 

139 }, 

140 }, 

141 }, 

142 }, 

143 } 

144 

145 paths[f"{prefix}/{{id}}"] = { 

146 "get": { 

147 "tags": [resource_name], 

148 "summary": f"Get {label} by ID", 

149 "operationId": f"get{resource_name.title()}", 

150 "parameters": [ 

151 { 

152 "name": "id", 

153 "in": "path", 

154 "required": True, 

155 "schema": {"type": "string"}, 

156 }, 

157 ], 

158 "responses": { 

159 "200": { 

160 "description": "Successful response", 

161 "content": { 

162 "application/json": { 

163 "schema": { 

164 "$ref": f"#/components/schemas/{schema_name}" 

165 }, 

166 }, 

167 }, 

168 }, 

169 }, 

170 }, 

171 "put": { 

172 "tags": [resource_name], 

173 "summary": f"Update {label}", 

174 "operationId": f"update{resource_name.title()}", 

175 "parameters": [ 

176 { 

177 "name": "id", 

178 "in": "path", 

179 "required": True, 

180 "schema": {"type": "string"}, 

181 }, 

182 ], 

183 "requestBody": { 

184 "required": True, 

185 "content": { 

186 "application/json": { 

187 "schema": {"$ref": f"#/components/schemas/{schema_name}"}, 

188 }, 

189 }, 

190 }, 

191 "responses": { 

192 "200": { 

193 "description": "Updated", 

194 "content": { 

195 "application/json": { 

196 "schema": { 

197 "$ref": f"#/components/schemas/{schema_name}" 

198 }, 

199 }, 

200 }, 

201 }, 

202 }, 

203 }, 

204 "delete": { 

205 "tags": [resource_name], 

206 "summary": f"Delete {label}", 

207 "operationId": f"delete{resource_name.title()}", 

208 "parameters": [ 

209 { 

210 "name": "id", 

211 "in": "path", 

212 "required": True, 

213 "schema": {"type": "string"}, 

214 }, 

215 ], 

216 "responses": { 

217 "204": {"description": "Deleted"}, 

218 }, 

219 }, 

220 } 

221 

222 spec: dict[str, Any] = { 

223 "openapi": "3.0.3", 

224 "info": { 

225 "title": title, 

226 "version": version, 

227 }, 

228 "tags": tags, 

229 "paths": paths, 

230 "components": { 

231 "schemas": schemas, 

232 }, 

233 } 

234 return spec 

235 

236 

237__all__ = ["resource_to_schema", "resources_to_openapi_spec"]