nerdd-module 0.3.3__tar.gz → 0.3.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nerdd_module-0.3.7/PKG-INFO +105 -0
- nerdd_module-0.3.7/README.md +18 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/__init__.py +0 -2
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/cli.py +63 -44
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/config/configuration.py +31 -12
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/config/default_configuration.py +3 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/config/merged_configuration.py +1 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/config/package_configuration.py +6 -7
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/config/search_yaml_configuration.py +2 -5
- nerdd_module-0.3.7/nerdd_module/config/yaml_configuration.py +90 -0
- nerdd_module-0.3.7/nerdd_module/converters/__init__.py +2 -0
- nerdd_module-0.3.7/nerdd_module/converters/converter.py +62 -0
- nerdd_module-0.3.7/nerdd_module/converters/identity_converter.py +8 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/__init__.py +0 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/depth_first_explorer.py +12 -10
- nerdd_module-0.3.7/nerdd_module/input/explorer.py +16 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/file_reader.py +7 -9
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/gzip_reader.py +4 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/inchi_reader.py +5 -7
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/list_reader.py +4 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/mol_reader.py +4 -6
- nerdd_module-0.3.7/nerdd_module/input/reader.py +58 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/sdf_reader.py +5 -8
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/smiles_reader.py +6 -10
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/string_reader.py +4 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/tar_reader.py +4 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/input/zip_reader.py +4 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/model/__init__.py +2 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/model/assign_mol_id_step.py +4 -2
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/model/assign_name_step.py +2 -4
- nerdd_module-0.3.7/nerdd_module/model/convert_representations_step.py +18 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/model/enforce_schema_step.py +3 -4
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/model/model.py +43 -63
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/model/read_input_step.py +4 -4
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/model/simple_model.py +32 -21
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/model/write_output_step.py +3 -3
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/output/__init__.py +1 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/output/csv_writer.py +2 -4
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/output/file_writer.py +5 -5
- nerdd_module-0.3.7/nerdd_module/output/iterator_writer.py +13 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/output/pandas_writer.py +4 -4
- nerdd_module-0.3.7/nerdd_module/output/record_list_writer.py +13 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/output/sdf_writer.py +1 -3
- nerdd_module-0.3.7/nerdd_module/output/writer.py +54 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/polyfills/__init__.py +1 -0
- nerdd_module-0.3.7/nerdd_module/polyfills/files.py +13 -0
- nerdd_module-0.3.7/nerdd_module/polyfills/get_entry_points.py +27 -0
- nerdd_module-0.3.7/nerdd_module/polyfills/types.py +14 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/preprocessing/check_valid_smiles.py +1 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/preprocessing/chembl_structure_pipeline.py +2 -2
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/preprocessing/filter_by_element.py +1 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/preprocessing/filter_by_weight.py +6 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/preprocessing/preprocessing_step.py +5 -5
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/preprocessing/remove_stereochemistry.py +2 -4
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/preprocessing/sanitize.py +4 -3
- nerdd_module-0.3.7/nerdd_module/problem.py +16 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/steps/map_step.py +6 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/steps/step.py +1 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/tests/__init__.py +0 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/tests/checks.py +31 -54
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/tests/models/AtomicMassModel.py +19 -5
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/tests/models/MolWeightModel.py +18 -12
- nerdd_module-0.3.7/nerdd_module/tests/predictions.py +56 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/tests/representations.py +1 -3
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/util/__init__.py +0 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/util/call_with_mappings.py +1 -3
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/util/package.py +2 -1
- nerdd_module-0.3.7/nerdd_module.egg-info/PKG-INFO +105 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module.egg-info/SOURCES.txt +5 -14
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module.egg-info/requires.txt +8 -4
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module.egg-info/top_level.txt +0 -1
- nerdd_module-0.3.7/pyproject.toml +134 -0
- nerdd_module-0.3.3/PKG-INFO +0 -77
- nerdd_module-0.3.3/README.md +0 -18
- nerdd_module-0.3.3/nerdd_module/config/yaml_configuration.py +0 -60
- nerdd_module-0.3.3/nerdd_module/converters/__init__.py +0 -2
- nerdd_module-0.3.3/nerdd_module/converters/converter.py +0 -16
- nerdd_module-0.3.3/nerdd_module/converters/converter_registry.py +0 -61
- nerdd_module-0.3.3/nerdd_module/converters/identity_converter.py +0 -5
- nerdd_module-0.3.3/nerdd_module/input/explorer.py +0 -16
- nerdd_module-0.3.3/nerdd_module/input/reader.py +0 -25
- nerdd_module-0.3.3/nerdd_module/input/reader_registry.py +0 -41
- nerdd_module-0.3.3/nerdd_module/model/add_smiles_step.py +0 -25
- nerdd_module-0.3.3/nerdd_module/output/iterator_writer.py +0 -13
- nerdd_module-0.3.3/nerdd_module/output/record_list_writer.py +0 -13
- nerdd_module-0.3.3/nerdd_module/output/writer.py +0 -18
- nerdd_module-0.3.3/nerdd_module/output/writer_registry.py +0 -50
- nerdd_module-0.3.3/nerdd_module/polyfills/files.py +0 -8
- nerdd_module-0.3.3/nerdd_module/polyfills/get_entry_points.py +0 -18
- nerdd_module-0.3.3/nerdd_module/problem.py +0 -17
- nerdd_module-0.3.3/nerdd_module/tests/predictions.py +0 -10
- nerdd_module-0.3.3/nerdd_module/tests/predictors.py +0 -42
- nerdd_module-0.3.3/nerdd_module/util/class_decorator.py +0 -29
- nerdd_module-0.3.3/nerdd_module.egg-info/PKG-INFO +0 -77
- nerdd_module-0.3.3/setup.py +0 -92
- nerdd_module-0.3.3/tests/conftest.py +0 -7
- nerdd_module-0.3.3/tests/steps/__init__.py +0 -3
- nerdd_module-0.3.3/tests/steps/checks.py +0 -77
- nerdd_module-0.3.3/tests/steps/input.py +0 -114
- nerdd_module-0.3.3/tests/steps/preprocessing.py +0 -111
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/LICENSE +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/config/__init__.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/config/dict_configuration.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/polyfills/version.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/preprocessing/__init__.py +0 -0
- /nerdd_module-0.3.3/tests/__init__.py → /nerdd_module-0.3.7/nerdd_module/py.typed +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/steps/__init__.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/steps/output_step.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/tests/models/__init__.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/tests/preprocessing/DummyPreprocessingStep.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/tests/preprocessing/__init__.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module/version.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/nerdd_module.egg-info/dependency_links.txt +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/setup.cfg +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.7}/tests/test_features.py +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nerdd-module
|
|
3
|
+
Version: 0.3.7
|
|
4
|
+
Summary: Base package to create NERDD modules
|
|
5
|
+
Author-email: Steffen Hirte <steffen.hirte@univie.ac.at>
|
|
6
|
+
Maintainer-email: Steffen Hirte <steffen.hirte@univie.ac.at>
|
|
7
|
+
License: BSD 3-Clause License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2023 - present, The Computational Drug Discovery and Design Group (COMP3D)
|
|
10
|
+
|
|
11
|
+
Redistribution and use in source and binary forms, with or without
|
|
12
|
+
modification, are permitted provided that the following conditions are met:
|
|
13
|
+
|
|
14
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
15
|
+
list of conditions and the following disclaimer.
|
|
16
|
+
|
|
17
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
18
|
+
this list of conditions and the following disclaimer in the documentation
|
|
19
|
+
and/or other materials provided with the distribution.
|
|
20
|
+
|
|
21
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
22
|
+
contributors may be used to endorse or promote products derived from
|
|
23
|
+
this software without specific prior written permission.
|
|
24
|
+
|
|
25
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
26
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
27
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
28
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
29
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
30
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
31
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
32
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
33
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
34
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
35
|
+
Project-URL: Repository, https://github.com/molinfo-vienna/nerdd-module
|
|
36
|
+
Keywords: science,research,development,nerdd
|
|
37
|
+
Classifier: Intended Audience :: Science/Research
|
|
38
|
+
Classifier: Intended Audience :: Developers
|
|
39
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
40
|
+
Classifier: Programming Language :: Python
|
|
41
|
+
Classifier: Topic :: Software Development
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering
|
|
43
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
44
|
+
Classifier: Operating System :: POSIX
|
|
45
|
+
Classifier: Operating System :: Unix
|
|
46
|
+
Classifier: Operating System :: MacOS
|
|
47
|
+
Classifier: Programming Language :: Python :: 3
|
|
48
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
49
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
50
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
51
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
License-File: LICENSE
|
|
54
|
+
Requires-Dist: pandas>=1.2.1
|
|
55
|
+
Requires-Dist: pyyaml>=6.0
|
|
56
|
+
Requires-Dist: filetype~=1.2.0
|
|
57
|
+
Requires-Dist: rich-click>=1.7.1
|
|
58
|
+
Requires-Dist: stringcase>=1.2.0
|
|
59
|
+
Requires-Dist: decorator>=5.1.1
|
|
60
|
+
Requires-Dist: importlib-resources>=5; python_version < "3.9"
|
|
61
|
+
Requires-Dist: importlib-metadata>=4.6; python_version < "3.10"
|
|
62
|
+
Provides-Extra: dev
|
|
63
|
+
Requires-Dist: mypy; extra == "dev"
|
|
64
|
+
Requires-Dist: ruff; extra == "dev"
|
|
65
|
+
Requires-Dist: pandas-stubs; extra == "dev"
|
|
66
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
67
|
+
Requires-Dist: types-decorator; extra == "dev"
|
|
68
|
+
Requires-Dist: types-setuptools; extra == "dev"
|
|
69
|
+
Provides-Extra: rdkit
|
|
70
|
+
Requires-Dist: rdkit>=2022.3.3; extra == "rdkit"
|
|
71
|
+
Provides-Extra: csp
|
|
72
|
+
Requires-Dist: chembl_structure_pipeline>=1.0.0; extra == "csp"
|
|
73
|
+
Provides-Extra: test
|
|
74
|
+
Requires-Dist: pytest; extra == "test"
|
|
75
|
+
Requires-Dist: pytest-sugar; extra == "test"
|
|
76
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
77
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
78
|
+
Requires-Dist: pytest-bdd; extra == "test"
|
|
79
|
+
Requires-Dist: pytest-mock; extra == "test"
|
|
80
|
+
Requires-Dist: pytest-watcher; extra == "test"
|
|
81
|
+
Requires-Dist: hypothesis; extra == "test"
|
|
82
|
+
Requires-Dist: hypothesis-rdkit; extra == "test"
|
|
83
|
+
Provides-Extra: docs
|
|
84
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
85
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
86
|
+
Requires-Dist: mkdocstrings; extra == "docs"
|
|
87
|
+
|
|
88
|
+
# NERDD Module
|
|
89
|
+
|
|
90
|
+
This package provides the basis to implement molecular prediction modules in the
|
|
91
|
+
NERDD ecosystem.
|
|
92
|
+
|
|
93
|
+
## Installation
|
|
94
|
+
|
|
95
|
+
``` bash
|
|
96
|
+
pip install -U nerdd-module
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Contribute
|
|
101
|
+
|
|
102
|
+
1. Fork and clone the code
|
|
103
|
+
2. Install test dependencies with ` pip install -e .[test,dev,csp]`
|
|
104
|
+
3. Run tests via `pytest` or `pytest-watch` (short: `ptw`)
|
|
105
|
+
4. Build docs via ` pip install -e .[docs]` and ` mkdocs serve`
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# NERDD Module
|
|
2
|
+
|
|
3
|
+
This package provides the basis to implement molecular prediction modules in the
|
|
4
|
+
NERDD ecosystem.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
``` bash
|
|
9
|
+
pip install -U nerdd-module
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Contribute
|
|
14
|
+
|
|
15
|
+
1. Fork and clone the code
|
|
16
|
+
2. Install test dependencies with ` pip install -e .[test,dev,csp]`
|
|
17
|
+
3. Run tests via `pytest` or `pytest-watch` (short: `ptw`)
|
|
18
|
+
4. Build docs via ` pip install -e .[docs]` and ` mkdocs serve`
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import os
|
|
3
2
|
import sys
|
|
3
|
+
from typing import Any, Callable
|
|
4
4
|
|
|
5
5
|
import rich_click as click
|
|
6
6
|
from decorator import decorator
|
|
7
7
|
from stringcase import spinalcase # type: ignore
|
|
8
8
|
|
|
9
|
+
from .model import Model
|
|
10
|
+
|
|
9
11
|
__all__ = ["auto_cli"]
|
|
10
12
|
|
|
11
13
|
input_description = """{description}
|
|
@@ -19,74 +21,70 @@ Note that input formats shouldn't be mixed.
|
|
|
19
21
|
"""
|
|
20
22
|
|
|
21
23
|
|
|
22
|
-
def infer_click_type(param):
|
|
24
|
+
def infer_click_type(param: dict) -> click.ParamType:
|
|
23
25
|
if "choices" in param:
|
|
24
26
|
choices = [c["value"] for c in param["choices"]]
|
|
25
27
|
return click.Choice(choices)
|
|
26
28
|
|
|
27
29
|
type_map = {
|
|
28
|
-
"float":
|
|
29
|
-
"int":
|
|
30
|
-
"str":
|
|
31
|
-
"bool":
|
|
30
|
+
"float": click.FLOAT,
|
|
31
|
+
"int": click.INT,
|
|
32
|
+
"str": click.STRING,
|
|
33
|
+
"bool": click.BOOL,
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
if "type" not in param:
|
|
37
|
+
raise ValueError(f"Parameter {param['name']} does not have a type")
|
|
38
|
+
|
|
39
|
+
t = param["type"]
|
|
40
|
+
|
|
41
|
+
if t not in type_map:
|
|
42
|
+
raise ValueError(f"Unknown type {t} for parameter {param['name']}")
|
|
43
|
+
|
|
44
|
+
return type_map[t]
|
|
35
45
|
|
|
36
46
|
|
|
37
47
|
@decorator
|
|
38
|
-
def auto_cli(f, *args, **kwargs):
|
|
48
|
+
def auto_cli(f: Callable[..., Model], *args: Any, **kwargs: Any) -> None:
|
|
39
49
|
# infer the command name
|
|
40
|
-
command_name = os.path.basename(sys.argv[0])
|
|
50
|
+
# command_name = os.path.basename(sys.argv[0])
|
|
41
51
|
|
|
42
52
|
# get the model
|
|
43
53
|
model = f()
|
|
44
54
|
|
|
45
|
-
config = model.get_config()
|
|
46
|
-
|
|
47
55
|
# compose cli description
|
|
48
|
-
description = config.get("description", "")
|
|
49
|
-
|
|
50
56
|
input_format_list = "\n".join([f"* {fmt}" for fmt in ["smiles", "sdf", "inchi"]])
|
|
51
57
|
|
|
52
58
|
help_text = input_description.format(
|
|
53
|
-
description=description, input_format_list=input_format_list
|
|
59
|
+
description=model.description, input_format_list=input_format_list
|
|
54
60
|
)
|
|
55
61
|
|
|
56
62
|
output_format_list = ["sdf", "csv"]
|
|
57
63
|
|
|
58
64
|
# compose footer with examples
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
# TODO: add examples
|
|
66
|
+
# examples = []
|
|
67
|
+
# if "example_smiles" in config:
|
|
68
|
+
# examples.append(config["example_smiles"])
|
|
69
|
+
|
|
70
|
+
# if len(examples) > 0:
|
|
71
|
+
# footer = "Examples:\n"
|
|
72
|
+
# for example in examples:
|
|
73
|
+
# footer += f'* {command_name} "{example}"\n'
|
|
74
|
+
# else:
|
|
75
|
+
# footer = ""
|
|
76
|
+
footer = ""
|
|
69
77
|
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
@click.command(context_settings={"show_default": True}, help=help_text)
|
|
74
|
-
@click.rich_config(
|
|
75
|
-
help_config=click.RichHelpConfiguration(
|
|
76
|
-
use_markdown=True,
|
|
77
|
-
show_metavars_column=False,
|
|
78
|
-
append_metavars_help=True,
|
|
79
|
-
footer_text=footer,
|
|
80
|
-
)
|
|
81
|
-
)
|
|
82
|
-
@click.argument("input", type=click.Path(), nargs=-1, required=True)
|
|
78
|
+
#
|
|
79
|
+
# Define the CLI entry point
|
|
80
|
+
#
|
|
83
81
|
def main(
|
|
84
|
-
input,
|
|
82
|
+
input: Any,
|
|
85
83
|
format: str,
|
|
86
84
|
output: click.Path,
|
|
87
85
|
log_level: str,
|
|
88
|
-
**kwargs,
|
|
89
|
-
):
|
|
86
|
+
**kwargs: Any,
|
|
87
|
+
) -> None:
|
|
90
88
|
logging.basicConfig(level=log_level.upper())
|
|
91
89
|
|
|
92
90
|
# write results
|
|
@@ -99,10 +97,15 @@ def auto_cli(f, *args, **kwargs):
|
|
|
99
97
|
|
|
100
98
|
model.predict(input, output_format=format, output_file=output_handle, **kwargs)
|
|
101
99
|
|
|
100
|
+
#
|
|
101
|
+
# Add required input parameter
|
|
102
|
+
#
|
|
103
|
+
main = click.argument("input", type=click.Path(), nargs=-1, required=True)(main)
|
|
104
|
+
|
|
102
105
|
#
|
|
103
106
|
# Add job parameters
|
|
104
107
|
#
|
|
105
|
-
for param in
|
|
108
|
+
for param in model.job_parameters:
|
|
106
109
|
# convert parameter name to spinal case (e.g. "max_confs" -> "max-confs")
|
|
107
110
|
param_name = spinalcase(param["name"])
|
|
108
111
|
main = click.option(
|
|
@@ -132,10 +135,26 @@ def auto_cli(f, *args, **kwargs):
|
|
|
132
135
|
main = click.option(
|
|
133
136
|
"--log-level",
|
|
134
137
|
default="warning",
|
|
135
|
-
type=click.Choice(
|
|
136
|
-
["debug", "info", "warning", "error", "critical"], case_sensitive=False
|
|
137
|
-
),
|
|
138
|
+
type=click.Choice(["debug", "info", "warning", "error", "critical"], case_sensitive=False),
|
|
138
139
|
help="The logging level.",
|
|
139
140
|
)(main)
|
|
140
141
|
|
|
142
|
+
#
|
|
143
|
+
# Create Rich command
|
|
144
|
+
#
|
|
145
|
+
|
|
146
|
+
# show_metavars_column=False: the column types are not in a separate column
|
|
147
|
+
# append_metavars_help=True: the column types are shown below the help text
|
|
148
|
+
main = click.rich_config(
|
|
149
|
+
help_config=click.RichHelpConfiguration(
|
|
150
|
+
use_markdown=True,
|
|
151
|
+
show_metavars_column=False,
|
|
152
|
+
append_metavars_help=True,
|
|
153
|
+
footer_text=footer,
|
|
154
|
+
)
|
|
155
|
+
)(main)
|
|
156
|
+
|
|
157
|
+
# show_default=True: default values are shown in the help text
|
|
158
|
+
main = click.command(context_settings={"show_default": True}, help=help_text)(main)
|
|
159
|
+
|
|
141
160
|
return main()
|
|
@@ -5,15 +5,34 @@ from typing import List
|
|
|
5
5
|
__all__ = ["Configuration"]
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def get_property_columns_of_type(config, t) -> List[dict]:
|
|
8
|
+
def get_property_columns_of_type(config: dict, t: str) -> List[dict]:
|
|
9
9
|
return [c for c in config["result_properties"] if c.get("level", "molecule") == t]
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def is_visible(result_property: dict, output_format: str) -> bool:
|
|
13
|
+
formats = result_property.get("formats", {})
|
|
14
|
+
|
|
15
|
+
if isinstance(formats, list):
|
|
16
|
+
return output_format in formats
|
|
17
|
+
elif isinstance(formats, dict):
|
|
18
|
+
include = formats.get("include", "*")
|
|
19
|
+
exclude = formats.get("exclude", [])
|
|
20
|
+
assert include == "*" or isinstance(
|
|
21
|
+
include, list
|
|
22
|
+
), f"Expected include to be a list or '*', got {include}"
|
|
23
|
+
assert isinstance(exclude, list), f"Expected exclude to be a list, got {exclude}"
|
|
24
|
+
return (include == "*" or output_format in include) and output_format not in exclude
|
|
25
|
+
else:
|
|
26
|
+
raise ValueError(
|
|
27
|
+
f"Invalid formats declaration {formats} in result property " f"{result_property}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
12
31
|
class Configuration(ABC):
|
|
13
|
-
def __init__(self):
|
|
32
|
+
def __init__(self) -> None:
|
|
14
33
|
pass
|
|
15
34
|
|
|
16
|
-
@lru_cache
|
|
35
|
+
@lru_cache(1)
|
|
17
36
|
def get_dict(self) -> dict:
|
|
18
37
|
config = self._get_dict()
|
|
19
38
|
|
|
@@ -22,9 +41,7 @@ class Configuration(ABC):
|
|
|
22
41
|
|
|
23
42
|
# check that a module can only predict atom or derivative properties, not both
|
|
24
43
|
num_atom_properties = len(get_property_columns_of_type(config, "atom"))
|
|
25
|
-
num_derivative_properties = len(
|
|
26
|
-
get_property_columns_of_type(config, "derivative")
|
|
27
|
-
)
|
|
44
|
+
num_derivative_properties = len(get_property_columns_of_type(config, "derivative"))
|
|
28
45
|
assert (
|
|
29
46
|
num_atom_properties == 0 or num_derivative_properties == 0
|
|
30
47
|
), "A module can only predict atom or derivative properties, not both."
|
|
@@ -39,13 +56,13 @@ class Configuration(ABC):
|
|
|
39
56
|
return self.get_dict() == {}
|
|
40
57
|
|
|
41
58
|
def molecular_property_columns(self) -> List[dict]:
|
|
42
|
-
return get_property_columns_of_type(self, "molecule")
|
|
59
|
+
return get_property_columns_of_type(self.get_dict(), "molecule")
|
|
43
60
|
|
|
44
61
|
def atom_property_columns(self) -> List[dict]:
|
|
45
|
-
return get_property_columns_of_type(self, "atom")
|
|
62
|
+
return get_property_columns_of_type(self.get_dict(), "atom")
|
|
46
63
|
|
|
47
64
|
def derivative_property_columns(self) -> List[dict]:
|
|
48
|
-
return get_property_columns_of_type(self, "derivative")
|
|
65
|
+
return get_property_columns_of_type(self.get_dict(), "derivative")
|
|
49
66
|
|
|
50
67
|
def get_task(self) -> str:
|
|
51
68
|
# if task is specified in the config, use that
|
|
@@ -64,8 +81,10 @@ class Configuration(ABC):
|
|
|
64
81
|
else:
|
|
65
82
|
return "molecular_property_prediction"
|
|
66
83
|
|
|
67
|
-
def
|
|
68
|
-
return
|
|
84
|
+
def get_visible_properties(self, output_format: str) -> List[dict]:
|
|
85
|
+
return [
|
|
86
|
+
p for p in self.get_dict().get("result_properties", []) if is_visible(p, output_format)
|
|
87
|
+
]
|
|
69
88
|
|
|
70
|
-
def __repr__(self):
|
|
89
|
+
def __repr__(self) -> str:
|
|
71
90
|
return f"{self.__class__.__name__}({self._get_dict()})"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
from stringcase import snakecase # type: ignore
|
|
2
4
|
|
|
3
5
|
from ..polyfills import version
|
|
@@ -7,7 +9,7 @@ __all__ = ["DefaultConfiguration"]
|
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class DefaultConfiguration(DictConfiguration):
|
|
10
|
-
def __init__(self, nerdd_module):
|
|
12
|
+
def __init__(self, nerdd_module: Any) -> None:
|
|
11
13
|
# generate a name from the module name
|
|
12
14
|
class_name = nerdd_module.__class__.__name__
|
|
13
15
|
if class_name.endswith("Model"):
|
|
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class PackageConfiguration(Configuration):
|
|
14
|
-
def __init__(self, package):
|
|
14
|
+
def __init__(self, package: str) -> None:
|
|
15
15
|
super().__init__()
|
|
16
16
|
|
|
17
17
|
# get the resource directory
|
|
@@ -20,17 +20,16 @@ class PackageConfiguration(Configuration):
|
|
|
20
20
|
except ModuleNotFoundError:
|
|
21
21
|
root_dir = None
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
else:
|
|
23
|
+
self.config: Configuration = DictConfiguration({})
|
|
24
|
+
if root_dir is not None:
|
|
26
25
|
# navigate to the config file
|
|
27
26
|
config_file = root_dir / "nerdd.yml"
|
|
28
27
|
|
|
29
|
-
if config_file is not None and config_file.
|
|
28
|
+
if config_file is not None and config_file.is_file():
|
|
30
29
|
logger.info(f"Found configuration file in package: {config_file}")
|
|
31
|
-
self.config = YamlConfiguration(config_file, base_path=root_dir)
|
|
30
|
+
self.config = YamlConfiguration(config_file.open(), base_path=root_dir)
|
|
32
31
|
else:
|
|
33
32
|
self.config = DictConfiguration({})
|
|
34
33
|
|
|
35
|
-
def _get_dict(self):
|
|
34
|
+
def _get_dict(self) -> dict:
|
|
36
35
|
return self.config.get_dict()
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
import
|
|
4
|
-
from typing import Any, Optional
|
|
3
|
+
from typing import Optional
|
|
5
4
|
|
|
6
5
|
from .configuration import Configuration
|
|
7
6
|
from .dict_configuration import DictConfiguration
|
|
@@ -32,9 +31,7 @@ class SearchYamlConfiguration(DictConfiguration):
|
|
|
32
31
|
leaf = os.path.dirname(leaf)
|
|
33
32
|
|
|
34
33
|
if default_config_file is not None:
|
|
35
|
-
logger.info(
|
|
36
|
-
f"Found configuration file in project directory: {default_config_file}"
|
|
37
|
-
)
|
|
34
|
+
logger.info(f"Found configuration file in project directory: {default_config_file}")
|
|
38
35
|
config = YamlConfiguration(default_config_file, base_path)
|
|
39
36
|
|
|
40
37
|
super().__init__(config.get_dict())
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import IO, Any, Union
|
|
5
|
+
|
|
6
|
+
import filetype # type: ignore
|
|
7
|
+
import yaml
|
|
8
|
+
from typing_extensions import Protocol
|
|
9
|
+
|
|
10
|
+
from ..polyfills import PathLikeStr, Traversable, as_file
|
|
11
|
+
from .configuration import Configuration
|
|
12
|
+
|
|
13
|
+
__all__ = ["YamlConfiguration"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CustomLoaderLike(Protocol):
|
|
17
|
+
base_path: Union[str, PathLikeStr, Traversable]
|
|
18
|
+
|
|
19
|
+
def construct_scalar(self, node: yaml.ScalarNode) -> str: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def image_constructor(loader: CustomLoaderLike, node: yaml.Node) -> str:
|
|
23
|
+
assert isinstance(node, yaml.ScalarNode)
|
|
24
|
+
|
|
25
|
+
base_path = loader.base_path
|
|
26
|
+
if isinstance(base_path, Traversable):
|
|
27
|
+
pass
|
|
28
|
+
else:
|
|
29
|
+
base_path = Path(base_path)
|
|
30
|
+
|
|
31
|
+
# obtain the actual file path from the scalar string node
|
|
32
|
+
filepath = loader.construct_scalar(node)
|
|
33
|
+
|
|
34
|
+
p = base_path / filepath
|
|
35
|
+
|
|
36
|
+
assert p.is_file(), f"File {p} does not exist"
|
|
37
|
+
|
|
38
|
+
# load the image from the provided logo path and convert it to base64
|
|
39
|
+
with as_file(p) as path:
|
|
40
|
+
with open(path, "rb") as f:
|
|
41
|
+
encoded = base64.b64encode(f.read()).decode("ascii")
|
|
42
|
+
|
|
43
|
+
# determine the file type from the file extension
|
|
44
|
+
kind = filetype.guess(f)
|
|
45
|
+
assert kind is not None
|
|
46
|
+
|
|
47
|
+
return f"data:{kind.mime};base64,{encoded}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class YamlConfiguration(Configuration):
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
path_or_handle: Union[str, PathLikeStr, IO[str]],
|
|
54
|
+
base_path: Union[str, PathLikeStr, Traversable, None] = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
super().__init__()
|
|
57
|
+
|
|
58
|
+
if isinstance(path_or_handle, str):
|
|
59
|
+
path_or_handle = Path(path_or_handle)
|
|
60
|
+
|
|
61
|
+
if base_path is None:
|
|
62
|
+
assert isinstance(path_or_handle, Path) and os.path.isfile(
|
|
63
|
+
path_or_handle
|
|
64
|
+
), f"File {path_or_handle} does not exist"
|
|
65
|
+
base_path = os.path.dirname(path_or_handle)
|
|
66
|
+
|
|
67
|
+
handle: IO[str]
|
|
68
|
+
if isinstance(path_or_handle, Path):
|
|
69
|
+
handle = path_or_handle.open()
|
|
70
|
+
elif hasattr(path_or_handle, "__fspath__"):
|
|
71
|
+
handle = open(path_or_handle)
|
|
72
|
+
else:
|
|
73
|
+
handle = path_or_handle
|
|
74
|
+
|
|
75
|
+
# we want to parse and process special tags (e.g. !image) in yaml files
|
|
76
|
+
# when loading a file with !image, the specified path should be relative to
|
|
77
|
+
# the yaml file itself
|
|
78
|
+
# --> need a custom loader that knows the path to the yaml file
|
|
79
|
+
class CustomLoader(yaml.SafeLoader, CustomLoaderLike):
|
|
80
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
81
|
+
super().__init__(*args, **kwargs)
|
|
82
|
+
assert base_path is not None, "base_path is None"
|
|
83
|
+
self.base_path = base_path
|
|
84
|
+
|
|
85
|
+
yaml.add_constructor("!image", image_constructor, CustomLoader)
|
|
86
|
+
|
|
87
|
+
self.yaml = yaml.load(handle, Loader=CustomLoader)
|
|
88
|
+
|
|
89
|
+
def _get_dict(self) -> dict:
|
|
90
|
+
return self.yaml["module"]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import Any, Callable, Dict, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from ..util import call_with_mappings
|
|
8
|
+
|
|
9
|
+
__all__ = ["Converter"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_factories: Dict[Tuple[Optional[str], Optional[str]], Callable[[dict], Converter]] = {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Converter(ABC):
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
super().__init__()
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def __init_subclass__(
|
|
21
|
+
cls,
|
|
22
|
+
output_format: Optional[str] = None,
|
|
23
|
+
data_type: Optional[str] = None,
|
|
24
|
+
is_abstract: bool = False,
|
|
25
|
+
**kwargs: Any,
|
|
26
|
+
) -> None:
|
|
27
|
+
super().__init_subclass__(**kwargs)
|
|
28
|
+
if not is_abstract:
|
|
29
|
+
_factories[(data_type, output_format)] = partial(call_with_mappings, cls)
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def _convert(self, input: Any, context: dict, **kwargs: Any) -> Any:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def convert(self, input: Any, context: dict, **kwargs: Any) -> Any:
|
|
36
|
+
return self._convert(input, context, **kwargs)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def get_converter(
|
|
40
|
+
cls,
|
|
41
|
+
data_type: str,
|
|
42
|
+
output_format: str,
|
|
43
|
+
return_default: bool = True,
|
|
44
|
+
**kwargs: Any,
|
|
45
|
+
) -> Converter:
|
|
46
|
+
if (data_type, output_format) not in _factories:
|
|
47
|
+
default = None
|
|
48
|
+
if return_default:
|
|
49
|
+
if (data_type, None) in _factories:
|
|
50
|
+
default = _factories[(data_type, None)]
|
|
51
|
+
elif (None, output_format) in _factories:
|
|
52
|
+
default = _factories[(None, output_format)]
|
|
53
|
+
elif (None, None) in _factories:
|
|
54
|
+
default = _factories[(None, None)]
|
|
55
|
+
|
|
56
|
+
if default is None:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Unknown data type '{data_type}' or output format '{output_format}'"
|
|
59
|
+
)
|
|
60
|
+
return default(kwargs)
|
|
61
|
+
|
|
62
|
+
return _factories[(data_type, output_format)](kwargs)
|