Passed
Push — main ( 4074a3...6b8d16 )
by Douglas
01:56
created

mandos.cli._write_exc_info()   A

Complexity

Conditions 3

Size

Total Lines 8
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nop 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
"""
2
Command-line interface for mandos.
3
"""
4
5
from __future__ import annotations
6
7
import os
8
import traceback
9
from pathlib import Path
10
from typing import Optional, Type
11
12
import orjson
0 ignored issues
show
introduced by
Unable to import 'orjson'
Loading history...
13
import typer
0 ignored issues
show
introduced by
Unable to import 'typer'
Loading history...
14
from pocketutils.core import DictNamespace
0 ignored issues
show
introduced by
Unable to import 'pocketutils.core'
Loading history...
15
from pocketutils.misc.loguru_utils import FancyLoguru
0 ignored issues
show
introduced by
Unable to import 'pocketutils.misc.loguru_utils'
Loading history...
16
from typer.models import CommandInfo
0 ignored issues
show
introduced by
Unable to import 'typer.models'
Loading history...
17
18
from mandos.model.utils.globals import Globals
19
20
cli = typer.Typer()
21
22
23
class CmdNamespace(DictNamespace):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
24
    @classmethod
25
    def make(cls) -> CmdNamespace:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
26
        from mandos.entry.calc_commands import CalcCommands
0 ignored issues
show
introduced by
Import outside toplevel (mandos.entry.calc_commands.CalcCommands)
Loading history...
27
        from mandos.entry.entry_commands import Entries
0 ignored issues
show
introduced by
Import outside toplevel (mandos.entry.entry_commands.Entries)
Loading history...
28
        from mandos.entry.misc_commands import (
0 ignored issues
show
introduced by
Import outside toplevel (mandos.entry.misc_commands.MiscCommands, mandos.entry.misc_commands._InsertedCommandListSingleton)
Loading history...
29
            MiscCommands,
30
            _InsertedCommandListSingleton,
31
        )
32
        from mandos.entry.plot_commands import PlotCommands
0 ignored issues
show
introduced by
Import outside toplevel (mandos.entry.plot_commands.PlotCommands)
Loading history...
33
34
        cli.registered_commands += [
35
            CommandInfo(":document", callback=MiscCommands.document),
36
            CommandInfo(":search", callback=MiscCommands.search),
37
            CommandInfo(":init", callback=MiscCommands.init, hidden=True),
38
            CommandInfo(":settings", callback=MiscCommands.list_settings, hidden=True),
39
            CommandInfo(":fill", callback=MiscCommands.fill),
40
            CommandInfo(":cache:data", callback=MiscCommands.cache_data),
41
            CommandInfo(":cache:taxa", callback=MiscCommands.cache_taxa),
42
            CommandInfo(":cache:g2p", callback=MiscCommands.cache_g2p),
43
            CommandInfo(":cache:clear", callback=MiscCommands.cache_clear),
44
            CommandInfo(":export:taxa", callback=MiscCommands.export_taxa),
45
            CommandInfo(":concat", callback=MiscCommands.concat),
46
            CommandInfo(":filter", callback=MiscCommands.filter),
47
            CommandInfo(":export:copy", callback=MiscCommands.export_copy),
48
            CommandInfo(":export:state", callback=MiscCommands.export_state),
49
            CommandInfo(":export:reify", callback=MiscCommands.export_reify),
50
            CommandInfo(":export:db", callback=MiscCommands.export_db, hidden=True),
51
            CommandInfo(":init-db", callback=MiscCommands.init_db, hidden=True),
52
            CommandInfo(":serve", callback=MiscCommands.serve, hidden=True),
53
            CommandInfo(":calc:enrichment", callback=CalcCommands.calc_enrichment),
54
            CommandInfo(":calc:phi", callback=CalcCommands.calc_phi),
55
            CommandInfo(":calc:psi", callback=CalcCommands.calc_psi),
56
            CommandInfo(":calc:ecfp", callback=CalcCommands.calc_ecfp, hidden=True),
57
            CommandInfo(":calc:psi-projection", callback=CalcCommands.calc_projection),
58
            CommandInfo(":calc:tau", callback=CalcCommands.calc_tau),
59
            CommandInfo(":plot:enrichment", callback=PlotCommands.plot_enrichment),
60
            CommandInfo(":plot:psi-projection", callback=PlotCommands.plot_projection),
61
            CommandInfo(":plot:psi-heatmap", callback=PlotCommands.plot_heatmap),
62
            CommandInfo(":plot:phi-vs-psi", callback=PlotCommands.plot_phi_psi),
63
            CommandInfo(":plot:tau", callback=PlotCommands.plot_tau),
64
        ]
65
        commands = {c.name: c for c in cli.registered_commands}
66
        # Oh dear this is a nightmare
67
        # it's really hard to create typer commands with dynamically configured params --
68
        # we really need to rely on its inferring of params
69
        # that makes this really hard to do well
70
        for entry in Entries:
71
            cmd = entry.cmd()
72
            info = CommandInfo(cmd, callback=entry.run)
73
            cli.registered_commands.append(info)
74
            # print(f"Registered {entry.cmd()} to {entry}")
75
            commands[cmd] = entry.run
76
        _InsertedCommandListSingleton.commands = cli.registered_commands
77
        return cls(**commands)
78
79
80
class MandosCli:
81
    """
82
    Global entry point for various stuff.
83
    """
84
85
    cli = cli
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable cli does not seem to be defined.
Loading history...
86
    commands = None
87
    log_setup: FancyLoguru = None
88
89
    @classmethod
90
    def as_library(cls) -> Type[MandosCli]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
91
        from mandos.model.utils.setup import LOG_SETUP
0 ignored issues
show
introduced by
Import outside toplevel (mandos.model.utils.setup.LOG_SETUP)
Loading history...
92
93
        Globals.is_cli = False
94
        cls.log_setup = (
95
            LOG_SETUP.set_control(False)
96
            .config_levels(
97
                levels=LOG_SETUP.defaults.levels_extended,
98
                icons=LOG_SETUP.defaults.icons_extended,
99
                colors=LOG_SETUP.defaults.colors_extended,
100
            )
101
            .add_log_methods()
102
        )
103
        cls.start()
104
        cls.commands = CmdNamespace.make()
105
        return cls
106
107
    @classmethod
108
    def as_cli(cls) -> Type[MandosCli]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
109
        from mandos.model.utils.setup import LOG_SETUP
0 ignored issues
show
introduced by
Import outside toplevel (mandos.model.utils.setup.LOG_SETUP)
Loading history...
110
111
        Globals.is_cli = True
112
        cls.log_setup = LOG_SETUP.logger.remove(None)
113
        (
114
            LOG_SETUP.set_control(True)
115
            .disable("chembl_webresource_client", "requests_cache", "urllib3", "numba")
116
            .config_levels(
117
                levels=LOG_SETUP.defaults.levels_extended,
118
                icons=LOG_SETUP.defaults.icons_extended,
119
                colors=LOG_SETUP.defaults.colors_red_green_safe,
120
            )
121
            .add_log_methods()
122
            .config_main(fmt=LOG_SETUP.defaults.fmt_simplified)
123
            .intercept_std()
124
        )
125
        cls.start()
126
        cls.commands = CmdNamespace.make()
127
        cls.init_apis()
128
        return cls
129
130
    @classmethod
131
    def init_apis(cls):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
132
        from mandos.entry.api_singletons import Apis
0 ignored issues
show
introduced by
Import outside toplevel (mandos.entry.api_singletons.Apis)
Loading history...
133
134
        Apis.set_default()
135
136
    @classmethod
137
    def start(cls):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
138
        from mandos import MandosMetadata
0 ignored issues
show
introduced by
Import outside toplevel (mandos.MandosMetadata)
Loading history...
139
        from mandos.model.utils.setup import logger
0 ignored issues
show
introduced by
Import outside toplevel (mandos.model.utils.setup.logger)
Loading history...
140
141
        if MandosMetadata.version is None:
142
            logger.error("Could not load package metadata for mandos. Is it installed?")
143
        else:
144
            logger.info(f"Mandos v{MandosMetadata.version}")
145
146
147
def _write_exc_info(mandos_: Type[MandosCli], e: BaseException) -> Path:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "e" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
148
    s_ = mandos_.log_setup
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s_" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
149
    s_.logger.exception(f"Command failed: {str(e)}")
150
    log_path_: Optional[Path] = next(iter(s_.paths)) if len(s_.paths) > 0 else None
151
    if log_path_ is None:
152
        log_path_ = Path(f"mandos-err-{Globals.start_timestamp_filesys}.log")
153
        log_path_.write_text(str(e) + os.linesep + traceback.format_exc(), encoding="utf8")
154
    return log_path_.resolve()
155
156
157
def _write_sys_info() -> Optional[Path]:
158
    try:
159
        from pocketutils.tools.filesys_tools import FilesysTools
0 ignored issues
show
introduced by
Unable to import 'pocketutils.tools.filesys_tools'
Loading history...
introduced by
Import outside toplevel (pocketutils.tools.filesys_tools.FilesysTools)
Loading history...
160
161
        info_ = FilesysTools.get_env_info()
162
        info_path_ = Path(f"mandos-err-{Globals.start_timestamp_filesys}-sys.json")
163
        info_ = orjson.dumps(info_, option=orjson.OPT_INDENT_2).decode(encoding="utf8")
164
        info_path_.write_text(info_, encoding="utf8")
165
        return info_path_.resolve()
166
    except BaseException:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as BaseException is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
167
        return None
168
169
170
def main() -> None:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
171
    mandos_ = MandosCli.as_cli()
172
    try:
173
        mandos_.cli()
174
    except KeyboardInterrupt:
175
        raise typer.Abort()
176
    except BaseException as e:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "e" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
177
        typer.echo(f"Command failed: {str(e)}", err=True)
178
        lg_path_ = _write_exc_info(mandos_, e)
179
        typer.echo(f"See {lg_path_} for details", err=True)
180
        inf_path_ = _write_sys_info()
181
        if inf_path_ is None:
182
            typer.echo(f"Could not get system info", err=True)
0 ignored issues
show
introduced by
Using an f-string that does not have any interpolated variables
Loading history...
183
        else:
184
            typer.echo(f"Wrote system info to {inf_path_}")
185
        raise typer.Exit(code=1)
186
187
188
if __name__ == "__main__":
189
    main()
190
191
192
__all__ = ["CmdNamespace", "MandosCli"]
193