Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from graphql import Document 

2from graphql import Definition 

3from graphql import OperationDefinition 

4from graphql import Field 

5from graphql import Selection 

6from graphql import Argument 

7from json import decode as json_decode 

8from json import Value as JsonValue 

9from .database import Database 

10from .database import Release 

11from .database import Package 

12from .statistics import Statistics 

13from .activities import Activities 

14from .activities import Activity 

15 

16class RequestError(Error): 

17 message: string 

18 

19class Response: 

20 _parts: [string] 

21 

22 func __init__(self): 

23 self._parts = [] 

24 

25 func format(self) -> string: 

26 return "".join(self._parts) 

27 

28 func object_begin(self): 

29 self._parts.append("{") 

30 

31 func object_end(self): 

32 if self._parts[-1] == ",": 

33 self._parts[-1] = "}" 

34 else: 

35 self._parts.append("}") 

36 

37 func object_append_key(self, key: string): 

38 self._parts.append(f"\"{key}\":") 

39 

40 func list_begin(self): 

41 self._parts.append("[") 

42 

43 func list_end(self): 

44 if self._parts[-1] == ",": 

45 self._parts[-1] = "]" 

46 else: 

47 self._parts.append("]") 

48 

49 func append(self, value: string): 

50 self._parts.append(value) 

51 

52 func append_string(self, value: string): 

53 self._parts.append(f"\"{value}\"") 

54 

55 func append_comma(self): 

56 self._parts.append(",") 

57 

58class GraphQL: 

59 _database: Database 

60 _statistics: Statistics 

61 _activities: Activities 

62 

63 func __init__(self, 

64 database: Database, 

65 statistics: Statistics, 

66 activities: Activities): 

67 self._database = database 

68 self._statistics = statistics 

69 self._activities = activities 

70 

71 func _get_package(self, name: string) -> Package: 

72 package = self._database.get_package(name) 

73 

74 if package is None: 

75 raise RequestError(f"Package '{name}' not found.") 

76 

77 return package 

78 

79 func _resolve_package_type(self, 

80 response: Response, 

81 package_name: string, 

82 selections: [Selection]?): 

83 if selections is None: 

84 raise RequestError("Bad package.") 

85 

86 package = self._get_package(package_name) 

87 response.object_begin() 

88 

89 for selection in selections: 

90 response.object_append_key(selection.field.name) 

91 

92 match selection.field.name: 

93 case "name": 

94 response.append_string(package.name) 

95 case "builds": 

96 match package.builds: 

97 case "yes": 

98 value = "true" 

99 case "no": 

100 value = "false" 

101 case _: 

102 value = "null" 

103 

104 response.append(value) 

105 case "numberOfDownloads": 

106 response.append(str(package.number_of_downloads)) 

107 case "coverage": 

108 total_path = self._database.make_path( 

109 f"standard-library/{package_name}/coverage/total.txt") 

110 

111 try: 

112 value = total_path.read_text().strip() 

113 except Error: 

114 value = "null" 

115 

116 response.append(value) 

117 case "linesOfCode": 

118 self._resolve_package_lines_of_code( 

119 response, 

120 package_name, 

121 selection.field.selections) 

122 case "latestRelease": 

123 self._resolve_package_latest_release( 

124 response, 

125 package.latest_release, 

126 selection.field.selections) 

127 case _ as name: 

128 raise RequestError(f"Bad field '{name}'.") 

129 

130 response.append_comma() 

131 

132 response.object_end() 

133 

134 func _resolve_package(self, 

135 response: Response, 

136 arguments: [Argument]?, 

137 selections: [Selection]?): 

138 if arguments is None: 

139 raise RequestError("Bad package.") 

140 

141 if arguments.length() != 1 or arguments[0].name != "name": 

142 raise RequestError("No package name.") 

143 

144 argument = arguments[0] 

145 

146 if argument.value is None: 

147 raise RequestError("No package name value.") 

148 

149 self._resolve_package_type(response, argument.value, selections) 

150 

151 func _resolve_package_latest_release(self, 

152 response: Response, 

153 release: Release, 

154 selections: [Selection]?): 

155 if selections is None: 

156 raise RequestError("Bad package latest release.") 

157 

158 response.object_begin() 

159 

160 for selection in selections: 

161 response.object_append_key(selection.field.name) 

162 

163 match selection.field.name: 

164 case "version": 

165 response.append_string(release.version) 

166 case "description": 

167 response.append_string(release.description) 

168 case _ as name: 

169 raise RequestError(f"Bad field '{name}'.") 

170 

171 response.append_comma() 

172 

173 response.object_end() 

174 

175 func _resolve_package_lines_of_code(self, 

176 response: Response, 

177 package_name: string, 

178 selections: [Selection]?): 

179 if selections is None: 

180 raise RequestError("Bad package lines of code.") 

181 

182 lines_of_code_path = self._database.make_path( 

183 f"package/{package_name}/lines_of_code.json") 

184 

185 try: 

186 lines_of_code = json_decode(lines_of_code_path.read_text()) 

187 except Error: 

188 response.append("null") 

189 return 

190 

191 response.object_begin() 

192 

193 for selection in selections: 

194 response.object_append_key(selection.field.name) 

195 

196 match selection.field.name: 

197 case "languages": 

198 self._resolve_package_lines_of_code_languages( 

199 response, 

200 selection.field.selections, 

201 lines_of_code) 

202 case "total": 

203 self._resolve_package_lines_of_code_total( 

204 response, 

205 selection.field.selections, 

206 lines_of_code) 

207 case _ as name: 

208 raise RequestError(f"Bad field '{name}'.") 

209 

210 response.append_comma() 

211 

212 response.object_end() 

213 

214 func _resolve_package_lines_of_code_languages(self, 

215 response: Response, 

216 selections: [Selection]?, 

217 lines_of_code: JsonValue): 

218 if selections is None: 

219 raise RequestError("Bad package lines of code.") 

220 

221 response.list_begin() 

222 

223 for language, data in lines_of_code.object(): 

224 if language in ["header", "SUM"]: 

225 continue 

226 

227 response.object_begin() 

228 

229 for selection in selections: 

230 response.object_append_key(selection.field.name) 

231 

232 match selection.field.name: 

233 case "name": 

234 response.append_string(language) 

235 case "data": 

236 self._resolve_package_lines_of_code_data( 

237 response, 

238 selection.field.selections, 

239 data) 

240 case _ as name: 

241 raise RequestError(f"Bad field '{name}'.") 

242 

243 response.append_comma() 

244 

245 response.object_end() 

246 response.append_comma() 

247 

248 response.list_end() 

249 

250 func _resolve_package_lines_of_code_total(self, 

251 response: Response, 

252 selections: [Selection]?, 

253 lines_of_code: JsonValue): 

254 if selections is None: 

255 raise RequestError("Bad package lines of code.") 

256 

257 self._resolve_package_lines_of_code_data(response, 

258 selections, 

259 lines_of_code.get("SUM")) 

260 

261 func _resolve_package_lines_of_code_data(self, 

262 response: Response, 

263 selections: [Selection]?, 

264 data: JsonValue): 

265 if selections is None: 

266 raise RequestError("Bad package lines of code data.") 

267 

268 response.object_begin() 

269 

270 for selection in selections: 

271 response.object_append_key(selection.field.name) 

272 

273 match selection.field.name: 

274 case "files": 

275 response.append(str(data.get("nFiles").integer())) 

276 case "blank": 

277 response.append(str(data.get("blank").integer())) 

278 case "comment": 

279 response.append(str(data.get("comment").integer())) 

280 case "code": 

281 response.append(str(data.get("code").integer())) 

282 case _ as name: 

283 raise RequestError(f"Bad field '{name}'.") 

284 

285 response.append_comma() 

286 

287 response.object_end() 

288 

289 func _resolve_standard_library(self, 

290 response: Response, 

291 selections: [Selection]?): 

292 if selections is None: 

293 raise RequestError("Bad standard_library.") 

294 

295 response.object_begin() 

296 

297 for selection in selections: 

298 response.object_append_key(selection.field.name) 

299 

300 match selection.field.name: 

301 case "package": 

302 self._resolve_package(response, 

303 selection.field.arguments, 

304 selection.field.selections) 

305 case "packages": 

306 self._resolve_packages(response, selection.field.selections) 

307 case "numberOfPackages": 

308 self._resolve_number_of_packages(response) 

309 case "numberOfDownloads": 

310 self._resolve_number_of_downloads(response) 

311 case _ as name: 

312 raise RequestError(f"Bad field '{name}'.") 

313 

314 response.append_comma() 

315 

316 response.object_end() 

317 

318 func _resolve_statistics(self, response: Response, selections: [Selection]?): 

319 if selections is None: 

320 raise RequestError("Bad statistics request.") 

321 

322 response.object_begin() 

323 

324 for selection in selections: 

325 response.object_append_key(selection.field.name) 

326 

327 match selection.field.name: 

328 case "startDateTime": 

329 response.append_string( 

330 str(self._statistics.start_date_time)) 

331 case "totalNumberOfRequests": 

332 response.append( 

333 str(self._statistics.number_of_requests)) 

334 case "numberOfUniqueVisitors": 

335 response.append( 

336 str(self._statistics.clients_ip_addresses.length())) 

337 case "numberOfGraphqlRequests": 

338 response.append( 

339 str(self._statistics.number_of_graphql_requests)) 

340 case "noIdleClientHandlers": 

341 response.append( 

342 str(self._statistics.no_idle_client_handlers)) 

343 case _ as name: 

344 raise RequestError(f"Bad field '{name}'.") 

345 

346 response.append_comma() 

347 

348 response.object_end() 

349 

350 func _resolve_activities(self, response: Response, selections: [Selection]): 

351 response.list_begin() 

352 

353 for activity in self._activities.recent(): 

354 self._resolve_activity_type(response, activity, selections) 

355 response.append_comma() 

356 

357 response.list_end() 

358 

359 func _resolve_activity_type(self, 

360 response: Response, 

361 activity: Activity, 

362 selections: [Selection]?): 

363 if selections is None: 

364 raise RequestError("Bad activity.") 

365 

366 response.object_begin() 

367 

368 for selection in selections: 

369 response.object_append_key(selection.field.name) 

370 

371 match selection.field.name: 

372 case "date": 

373 response.append_string(activity.date) 

374 case "kind": 

375 response.append_string(activity.kind) 

376 case "message": 

377 response.append_string(activity.message) 

378 case _ as name: 

379 raise RequestError(f"Bad field '{name}'.") 

380 

381 response.append_comma() 

382 

383 response.object_end() 

384 

385 func _resolve_packages(self, response: Response, selections: [Selection]?): 

386 if selections is None: 

387 raise RequestError("Bad packages request.") 

388 

389 response.list_begin() 

390 

391 for name in self._database.get_packages(): 

392 self._resolve_package_type(response, name, selections) 

393 response.append_comma() 

394 

395 response.list_end() 

396 

397 func _resolve_number_of_packages(self, response: Response): 

398 response.append(str(self._database.get_packages().length())) 

399 

400 func _resolve_number_of_downloads(self, response: Response): 

401 number_of_downloads = 0 

402 

403 for name in self._database.get_packages(): 

404 number_of_downloads += self._get_package(name).number_of_downloads 

405 

406 response.append(str(number_of_downloads)) 

407 

408 func resolve_query(self, document: Document) -> string: 

409 if document.definitions.length() == 0: 

410 raise RequestError("No definition.") 

411 

412 selections = (document 

413 .definitions[0] 

414 .executable_definition 

415 .operation_definition 

416 .selections) 

417 response = Response() 

418 response.object_begin() 

419 response.object_append_key("data") 

420 response.object_begin() 

421 

422 for selection in selections: 

423 response.object_append_key(selection.field.name) 

424 

425 match selection.field.name: 

426 case "statistics": 

427 self._resolve_statistics(response, selection.field.selections) 

428 case "standardLibrary": 

429 self._resolve_standard_library(response, 

430 selection.field.selections) 

431 case "activities": 

432 self._resolve_activities(response, selection.field.selections) 

433 case _ as name: 

434 raise RequestError(f"Bad field '{name}'.") 

435 

436 response.append_comma() 

437 

438 response.object_end() 

439 response.object_end() 

440 

441 return response.format()