|
1 | 1 | import io |
2 | 2 | import os |
3 | | -from typing import Dict |
| 3 | +from multiprocessing import Process, Queue |
| 4 | +from queue import Empty |
| 5 | +from typing import Dict, List, Tuple |
4 | 6 | from zipfile import ZipFile |
5 | 7 |
|
6 | | -from beet import Context, ProjectConfig, config_error_handler, run_beet |
| 8 | +from beet import ( |
| 9 | + Context, |
| 10 | + FormattedPipelineException, |
| 11 | + ProjectConfig, |
| 12 | + config_error_handler, |
| 13 | + run_beet, |
| 14 | +) |
7 | 15 | from beet.core.utils import JsonDict |
| 16 | +from beet.toolchain.utils import format_exc |
8 | 17 | from lectern import Document |
9 | 18 |
|
| 19 | +BuildResult = Tuple[List[str], Dict[str, bytes]] |
| 20 | + |
10 | 21 |
|
11 | 22 | def generate_packs( |
| 23 | + project_config: JsonDict, |
| 24 | + build_timeout: float, |
| 25 | + project_name: str, |
| 26 | + message_content: str, |
| 27 | +) -> BuildResult: |
| 28 | + q: "Queue[BuildResult]" = Queue() |
| 29 | + |
| 30 | + p = Process(target=worker, args=(q, project_name, project_config, message_content)) |
| 31 | + p.start() |
| 32 | + p.join(timeout=build_timeout) |
| 33 | + |
| 34 | + try: |
| 35 | + return q.get_nowait() |
| 36 | + except Empty: |
| 37 | + p.kill() |
| 38 | + return [ |
| 39 | + f"Timeout exceeded. The message took more than {build_timeout} seconds to process." |
| 40 | + ], {} |
| 41 | + |
| 42 | + |
| 43 | +def worker( |
| 44 | + q: "Queue[BuildResult]", |
12 | 45 | project_name: str, |
13 | 46 | project_config: JsonDict, |
14 | 47 | message_content: str, |
15 | | -) -> Dict[str, bytes]: |
| 48 | +): |
16 | 49 | project_directory = os.getcwd() |
17 | | - packs: Dict[str, bytes] = {} |
18 | 50 |
|
19 | | - message_config = { |
| 51 | + base_config = { |
20 | 52 | "name": project_name, |
21 | 53 | "pipeline": [__name__], |
22 | 54 | "meta": { |
23 | 55 | "source": message_content, |
| 56 | + "build_output": [], |
| 57 | + "build_attachments": {}, |
24 | 58 | }, |
25 | 59 | } |
26 | 60 |
|
27 | | - with config_error_handler(): |
28 | | - config = ( |
29 | | - ProjectConfig(**project_config) |
30 | | - .resolve(project_directory) |
31 | | - .with_defaults(ProjectConfig(**message_config).resolve(project_directory)) |
32 | | - ) |
| 61 | + try: |
| 62 | + with config_error_handler(): |
| 63 | + config = ( |
| 64 | + ProjectConfig(**project_config) |
| 65 | + .resolve(project_directory) |
| 66 | + .with_defaults(ProjectConfig(**base_config).resolve(project_directory)) |
| 67 | + ) |
| 68 | + |
| 69 | + with run_beet(config) as ctx: |
| 70 | + build_output = ctx.meta["build_output"] |
| 71 | + attachments = ctx.meta["build_attachments"] |
| 72 | + |
| 73 | + for pack in ctx.packs: |
| 74 | + if pack: |
| 75 | + fp = io.BytesIO() |
33 | 76 |
|
34 | | - with run_beet(config) as ctx: |
35 | | - for pack in ctx.packs: |
36 | | - if not pack: |
37 | | - continue |
| 77 | + with ZipFile(fp, mode="w") as output: |
| 78 | + pack.dump(output) |
| 79 | + output.writestr("source.md", message_content) |
38 | 80 |
|
39 | | - fp = io.BytesIO() |
| 81 | + attachments[f"{pack.name}.zip"] = fp.getvalue() |
40 | 82 |
|
41 | | - with ZipFile(fp, mode="w") as output: |
42 | | - pack.dump(output) |
43 | | - output.writestr("source.md", message_content) |
| 83 | + q.put((build_output, attachments)) |
| 84 | + return |
| 85 | + except FormattedPipelineException as exc: |
| 86 | + build_output = [exc.message] |
| 87 | + exception = exc.__cause__ if exc.format_cause else None |
| 88 | + except Exception as exc: |
| 89 | + build_output = ["An unhandled exception occurred. This could be a bug."] |
| 90 | + exception = exc |
44 | 91 |
|
45 | | - packs[f"{pack.name}.zip"] = fp.getvalue() |
| 92 | + if exception: |
| 93 | + build_output.append(format_exc(exception)) |
46 | 94 |
|
47 | | - return packs |
| 95 | + q.put((build_output, {})) |
48 | 96 |
|
49 | 97 |
|
50 | 98 | def beet_default(ctx: Context): |
|
0 commit comments