Coverage for src/graphql.mys : 85%

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
16class RequestError(Error):
17 message: string
19class Response:
20 _parts: [string]
22 func __init__(self):
23 self._parts = []
25 func format(self) -> string:
26 return "".join(self._parts)
28 func object_begin(self):
29 self._parts.append("{")
31 func object_end(self):
32 if self._parts[-1] == ",":
33 self._parts[-1] = "}"
34 else:
35 self._parts.append("}")
37 func object_append_key(self, key: string):
38 self._parts.append(f"\"{key}\":")
40 func list_begin(self):
41 self._parts.append("[")
43 func list_end(self):
44 if self._parts[-1] == ",":
45 self._parts[-1] = "]"
46 else:
47 self._parts.append("]")
49 func append(self, value: string):
50 self._parts.append(value)
52 func append_string(self, value: string):
53 self._parts.append(f"\"{value}\"")
55 func append_comma(self):
56 self._parts.append(",")
58class GraphQL:
59 _database: Database
60 _statistics: Statistics
61 _activities: Activities
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
71 func _get_package(self, name: string) -> Package:
72 package = self._database.get_package(name)
74 if package is None:
75 raise RequestError(f"Package '{name}' not found.")
77 return package
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.")
86 package = self._get_package(package_name)
87 response.object_begin()
89 for selection in selections:
90 response.object_append_key(selection.field.name)
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"
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")
111 try:
112 value = total_path.read_text().strip()
113 except Error:
114 value = "null"
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}'.")
130 response.append_comma()
132 response.object_end()
134 func _resolve_package(self,
135 response: Response,
136 arguments: [Argument]?,
137 selections: [Selection]?):
138 if arguments is None:
139 raise RequestError("Bad package.")
141 if arguments.length() != 1 or arguments[0].name != "name":
142 raise RequestError("No package name.")
144 argument = arguments[0]
146 if argument.value is None:
147 raise RequestError("No package name value.")
149 self._resolve_package_type(response, argument.value, selections)
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.")
158 response.object_begin()
160 for selection in selections:
161 response.object_append_key(selection.field.name)
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}'.")
171 response.append_comma()
173 response.object_end()
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.")
182 lines_of_code_path = self._database.make_path(
183 f"package/{package_name}/lines_of_code.json")
185 try:
186 lines_of_code = json_decode(lines_of_code_path.read_text())
187 except Error:
188 response.append("null")
189 return
191 response.object_begin()
193 for selection in selections:
194 response.object_append_key(selection.field.name)
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}'.")
210 response.append_comma()
212 response.object_end()
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.")
221 response.list_begin()
223 for language, data in lines_of_code.object():
224 if language in ["header", "SUM"]:
225 continue
227 response.object_begin()
229 for selection in selections:
230 response.object_append_key(selection.field.name)
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}'.")
243 response.append_comma()
245 response.object_end()
246 response.append_comma()
248 response.list_end()
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.")
257 self._resolve_package_lines_of_code_data(response,
258 selections,
259 lines_of_code.get("SUM"))
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.")
268 response.object_begin()
270 for selection in selections:
271 response.object_append_key(selection.field.name)
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}'.")
285 response.append_comma()
287 response.object_end()
289 func _resolve_standard_library(self,
290 response: Response,
291 selections: [Selection]?):
292 if selections is None:
293 raise RequestError("Bad standard_library.")
295 response.object_begin()
297 for selection in selections:
298 response.object_append_key(selection.field.name)
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}'.")
314 response.append_comma()
316 response.object_end()
318 func _resolve_statistics(self, response: Response, selections: [Selection]?):
319 if selections is None:
320 raise RequestError("Bad statistics request.")
322 response.object_begin()
324 for selection in selections:
325 response.object_append_key(selection.field.name)
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}'.")
346 response.append_comma()
348 response.object_end()
350 func _resolve_activities(self, response: Response, selections: [Selection]):
351 response.list_begin()
353 for activity in self._activities.recent():
354 self._resolve_activity_type(response, activity, selections)
355 response.append_comma()
357 response.list_end()
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.")
366 response.object_begin()
368 for selection in selections:
369 response.object_append_key(selection.field.name)
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}'.")
381 response.append_comma()
383 response.object_end()
385 func _resolve_packages(self, response: Response, selections: [Selection]?):
386 if selections is None:
387 raise RequestError("Bad packages request.")
389 response.list_begin()
391 for name in self._database.get_packages():
392 self._resolve_package_type(response, name, selections)
393 response.append_comma()
395 response.list_end()
397 func _resolve_number_of_packages(self, response: Response):
398 response.append(str(self._database.get_packages().length()))
400 func _resolve_number_of_downloads(self, response: Response):
401 number_of_downloads = 0
403 for name in self._database.get_packages():
404 number_of_downloads += self._get_package(name).number_of_downloads
406 response.append(str(number_of_downloads))
408 func resolve_query(self, document: Document) -> string:
409 if document.definitions.length() == 0:
410 raise RequestError("No definition.")
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()
422 for selection in selections:
423 response.object_append_key(selection.field.name)
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}'.")
436 response.append_comma()
438 response.object_end()
439 response.object_end()
441 return response.format()