nerdd-module 0.3.3__tar.gz → 0.3.24__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.24/PKG-INFO +110 -0
- nerdd_module-0.3.24/README.md +19 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/__init__.py +2 -2
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/cli.py +74 -43
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/__init__.py +1 -0
- nerdd_module-0.3.24/nerdd_module/config/configuration.py +32 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/default_configuration.py +3 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/merged_configuration.py +2 -2
- nerdd_module-0.3.24/nerdd_module/config/models.py +199 -0
- nerdd_module-0.3.24/nerdd_module/config/package_configuration.py +37 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/search_yaml_configuration.py +8 -9
- nerdd_module-0.3.24/nerdd_module/config/yaml_configuration.py +98 -0
- nerdd_module-0.3.24/nerdd_module/converters/__init__.py +8 -0
- nerdd_module-0.3.24/nerdd_module/converters/basic_type_converter.py +23 -0
- nerdd_module-0.3.24/nerdd_module/converters/converter.py +109 -0
- nerdd_module-0.3.24/nerdd_module/converters/converter_config.py +15 -0
- nerdd_module-0.3.24/nerdd_module/converters/mol_converter.py +20 -0
- nerdd_module-0.3.24/nerdd_module/converters/problem_list_converter.py +39 -0
- nerdd_module-0.3.24/nerdd_module/converters/representation_converter.py +48 -0
- nerdd_module-0.3.24/nerdd_module/converters/source_list_converter.py +29 -0
- nerdd_module-0.3.24/nerdd_module/converters/void_converter.py +17 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/__init__.py +0 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/depth_first_explorer.py +16 -15
- nerdd_module-0.3.24/nerdd_module/input/explorer.py +13 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/file_reader.py +19 -13
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/gzip_reader.py +4 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/inchi_reader.py +15 -10
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/list_reader.py +4 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/mol_reader.py +5 -7
- nerdd_module-0.3.24/nerdd_module/input/reader.py +52 -0
- nerdd_module-0.3.24/nerdd_module/input/reader_config.py +9 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/sdf_reader.py +8 -11
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/smiles_reader.py +12 -13
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/string_reader.py +4 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/tar_reader.py +5 -7
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/zip_reader.py +5 -7
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/__init__.py +2 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/assign_mol_id_step.py +4 -2
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/assign_name_step.py +2 -4
- nerdd_module-0.3.24/nerdd_module/model/convert_representations_step.py +26 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/enforce_schema_step.py +8 -7
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/model.py +45 -64
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/read_input_step.py +5 -5
- nerdd_module-0.3.24/nerdd_module/model/simple_model.py +217 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/write_output_step.py +6 -3
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/__init__.py +2 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/csv_writer.py +2 -4
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/file_writer.py +5 -5
- nerdd_module-0.3.24/nerdd_module/output/iterator_writer.py +13 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/pandas_writer.py +4 -4
- nerdd_module-0.3.24/nerdd_module/output/record_list_writer.py +13 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/sdf_writer.py +14 -7
- nerdd_module-0.3.24/nerdd_module/output/writer.py +58 -0
- nerdd_module-0.3.24/nerdd_module/polyfills/__init__.py +7 -0
- nerdd_module-0.3.24/nerdd_module/polyfills/block_logs.py +46 -0
- nerdd_module-0.3.24/nerdd_module/polyfills/files.py +13 -0
- nerdd_module-0.3.24/nerdd_module/polyfills/get_entry_points.py +27 -0
- nerdd_module-0.3.24/nerdd_module/polyfills/literal.py +8 -0
- nerdd_module-0.3.24/nerdd_module/polyfills/typed_dict.py +8 -0
- nerdd_module-0.3.24/nerdd_module/polyfills/types.py +14 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/check_valid_smiles.py +1 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/chembl_structure_pipeline.py +14 -15
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/filter_by_element.py +6 -16
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/filter_by_weight.py +8 -11
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/preprocessing_step.py +5 -5
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/remove_stereochemistry.py +2 -4
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/sanitize.py +4 -3
- nerdd_module-0.3.24/nerdd_module/problem.py +42 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/steps/map_step.py +6 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/steps/step.py +1 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/__init__.py +1 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/checks.py +31 -54
- nerdd_module-0.3.3/tests/steps/input.py → nerdd_module-0.3.24/nerdd_module/tests/files.py +13 -53
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/models/AtomicMassModel.py +22 -5
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/models/MolWeightModel.py +19 -11
- nerdd_module-0.3.24/nerdd_module/tests/predictions.py +58 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/representations.py +14 -11
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/util/__init__.py +0 -1
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/util/call_with_mappings.py +6 -5
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/util/package.py +2 -1
- nerdd_module-0.3.24/nerdd_module.egg-info/PKG-INFO +110 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module.egg-info/SOURCES.txt +18 -15
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module.egg-info/requires.txt +16 -6
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module.egg-info/top_level.txt +0 -1
- nerdd_module-0.3.24/pyproject.toml +151 -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/configuration.py +0 -71
- nerdd_module-0.3.3/nerdd_module/config/package_configuration.py +0 -36
- 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/model/simple_model.py +0 -158
- 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/__init__.py +0 -3
- 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/preprocessing.py +0 -111
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/LICENSE +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/dict_configuration.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/polyfills/version.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/__init__.py +0 -0
- /nerdd_module-0.3.3/tests/__init__.py → /nerdd_module-0.3.24/nerdd_module/py.typed +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/steps/__init__.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/steps/output_step.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/models/__init__.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/preprocessing/DummyPreprocessingStep.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/preprocessing/__init__.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/version.py +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module.egg-info/dependency_links.txt +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/setup.cfg +0 -0
- {nerdd_module-0.3.3 → nerdd_module-0.3.24}/tests/test_features.py +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: nerdd-module
|
|
3
|
+
Version: 0.3.24
|
|
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: pydantic>=2
|
|
61
|
+
Requires-Dist: importlib-resources>=5; python_version < "3.9"
|
|
62
|
+
Requires-Dist: importlib-metadata>=4.6; python_version < "3.10"
|
|
63
|
+
Requires-Dist: typing_extensions>=4.0.1; python_version < "3.8"
|
|
64
|
+
Provides-Extra: dev
|
|
65
|
+
Requires-Dist: mypy>=1; extra == "dev"
|
|
66
|
+
Requires-Dist: ruff==0.7.1; extra == "dev"
|
|
67
|
+
Requires-Dist: pandas-stubs; extra == "dev"
|
|
68
|
+
Requires-Dist: rdkit-stubs; extra == "dev"
|
|
69
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
70
|
+
Requires-Dist: types-decorator; extra == "dev"
|
|
71
|
+
Requires-Dist: types-setuptools; extra == "dev"
|
|
72
|
+
Requires-Dist: pre-commit>=2; extra == "dev"
|
|
73
|
+
Provides-Extra: rdkit
|
|
74
|
+
Requires-Dist: rdkit>=2022.3.3; extra == "rdkit"
|
|
75
|
+
Provides-Extra: csp
|
|
76
|
+
Requires-Dist: chembl_structure_pipeline>=1.0.0; extra == "csp"
|
|
77
|
+
Provides-Extra: test
|
|
78
|
+
Requires-Dist: pytest; extra == "test"
|
|
79
|
+
Requires-Dist: pytest-sugar; extra == "test"
|
|
80
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
81
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
82
|
+
Requires-Dist: pytest-bdd<8; extra == "test"
|
|
83
|
+
Requires-Dist: pytest-mock; extra == "test"
|
|
84
|
+
Requires-Dist: pytest-watcher; extra == "test"
|
|
85
|
+
Requires-Dist: hypothesis; extra == "test"
|
|
86
|
+
Requires-Dist: hypothesis-rdkit; extra == "test"
|
|
87
|
+
Provides-Extra: docs
|
|
88
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
89
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
90
|
+
Requires-Dist: mkdocstrings; extra == "docs"
|
|
91
|
+
|
|
92
|
+
# NERDD Module
|
|
93
|
+
|
|
94
|
+
This package provides the basis to implement molecular prediction modules in the
|
|
95
|
+
NERDD ecosystem.
|
|
96
|
+
|
|
97
|
+
## Installation
|
|
98
|
+
|
|
99
|
+
``` bash
|
|
100
|
+
pip install -U nerdd-module
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## Contribute
|
|
105
|
+
|
|
106
|
+
1. Fork and clone the code
|
|
107
|
+
2. Install test dependencies with `pip install -e .[test,dev,csp]`
|
|
108
|
+
3. Install pre-commit hooks `pre-commit install`
|
|
109
|
+
4. Run tests via `pytest` or `pytest-watch` (short: `ptw`)
|
|
110
|
+
5. Build docs via `pip install -e .[docs]` and `mkdocs serve`
|
|
@@ -0,0 +1,19 @@
|
|
|
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. Install pre-commit hooks `pre-commit install`
|
|
18
|
+
4. Run tests via `pytest` or `pytest-watch` (short: `ptw`)
|
|
19
|
+
5. Build docs via `pip install -e .[docs]` and `mkdocs serve`
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
|
+
from typing import Any, Callable
|
|
4
5
|
|
|
5
6
|
import rich_click as click
|
|
6
7
|
from decorator import decorator
|
|
7
|
-
from stringcase import spinalcase
|
|
8
|
+
from stringcase import spinalcase
|
|
9
|
+
|
|
10
|
+
from .config import JobParameter
|
|
11
|
+
from .input import Reader
|
|
12
|
+
from .model import Model
|
|
13
|
+
from .output import FileWriter, Writer
|
|
8
14
|
|
|
9
15
|
__all__ = ["auto_cli"]
|
|
10
16
|
|
|
11
17
|
input_description = """{description}
|
|
12
18
|
|
|
13
|
-
INPUT molecules are provided as file paths or strings. The following formats are
|
|
19
|
+
INPUT molecules are provided as file paths or strings. The following formats are
|
|
14
20
|
supported:
|
|
15
21
|
|
|
16
22
|
{input_format_list}
|
|
@@ -19,46 +25,60 @@ Note that input formats shouldn't be mixed.
|
|
|
19
25
|
"""
|
|
20
26
|
|
|
21
27
|
|
|
22
|
-
def infer_click_type(param):
|
|
23
|
-
if
|
|
24
|
-
choices = [c
|
|
28
|
+
def infer_click_type(param: JobParameter) -> click.ParamType:
|
|
29
|
+
if param.choices is not None:
|
|
30
|
+
choices = [c.value for c in param.choices]
|
|
25
31
|
return click.Choice(choices)
|
|
26
32
|
|
|
27
33
|
type_map = {
|
|
28
|
-
"float":
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"bool":
|
|
34
|
+
"float": click.FLOAT,
|
|
35
|
+
"integer": click.INT,
|
|
36
|
+
"string": click.STRING,
|
|
37
|
+
"bool": click.BOOL,
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
t = param.type
|
|
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
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
|
-
output_format_list = [
|
|
62
|
+
output_format_list = [
|
|
63
|
+
output_format
|
|
64
|
+
for output_format, writer in Writer.get_writers(output_file=None).items()
|
|
65
|
+
if isinstance(writer, FileWriter)
|
|
66
|
+
]
|
|
57
67
|
|
|
58
68
|
# compose footer with examples
|
|
59
69
|
examples = []
|
|
60
|
-
if "
|
|
61
|
-
|
|
70
|
+
if hasattr(model, "get_config"):
|
|
71
|
+
example_smiles = model.get_config().example_smiles
|
|
72
|
+
if example_smiles is not None:
|
|
73
|
+
examples.append(example_smiles)
|
|
74
|
+
|
|
75
|
+
for ReaderClass in Reader.get_reader_mapping():
|
|
76
|
+
if hasattr(ReaderClass, "config"):
|
|
77
|
+
reader_examples = ReaderClass.config.get("examples", [])
|
|
78
|
+
for example in reader_examples:
|
|
79
|
+
# check if example fits on one line
|
|
80
|
+
if len(example) < 120 and "\n" not in example:
|
|
81
|
+
examples.append(example)
|
|
62
82
|
|
|
63
83
|
if len(examples) > 0:
|
|
64
84
|
footer = "Examples:\n"
|
|
@@ -67,26 +87,16 @@ def auto_cli(f, *args, **kwargs):
|
|
|
67
87
|
else:
|
|
68
88
|
footer = ""
|
|
69
89
|
|
|
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)
|
|
90
|
+
#
|
|
91
|
+
# Define the CLI entry point
|
|
92
|
+
#
|
|
83
93
|
def main(
|
|
84
|
-
input,
|
|
94
|
+
input: Any,
|
|
85
95
|
format: str,
|
|
86
96
|
output: click.Path,
|
|
87
97
|
log_level: str,
|
|
88
|
-
**kwargs,
|
|
89
|
-
):
|
|
98
|
+
**kwargs: Any,
|
|
99
|
+
) -> None:
|
|
90
100
|
logging.basicConfig(level=log_level.upper())
|
|
91
101
|
|
|
92
102
|
# write results
|
|
@@ -99,17 +109,22 @@ def auto_cli(f, *args, **kwargs):
|
|
|
99
109
|
|
|
100
110
|
model.predict(input, output_format=format, output_file=output_handle, **kwargs)
|
|
101
111
|
|
|
112
|
+
#
|
|
113
|
+
# Add required input parameter
|
|
114
|
+
#
|
|
115
|
+
main = click.argument("input", type=click.Path(), nargs=-1, required=True)(main)
|
|
116
|
+
|
|
102
117
|
#
|
|
103
118
|
# Add job parameters
|
|
104
119
|
#
|
|
105
|
-
for param in
|
|
120
|
+
for param in model.job_parameters:
|
|
106
121
|
# convert parameter name to spinal case (e.g. "max_confs" -> "max-confs")
|
|
107
|
-
param_name = spinalcase(param
|
|
122
|
+
param_name = spinalcase(param.name)
|
|
108
123
|
main = click.option(
|
|
109
124
|
f"--{param_name}",
|
|
110
|
-
default=param.
|
|
125
|
+
default=param.default,
|
|
111
126
|
type=infer_click_type(param),
|
|
112
|
-
help=param.
|
|
127
|
+
help=param.help_text,
|
|
113
128
|
)(main)
|
|
114
129
|
|
|
115
130
|
#
|
|
@@ -132,10 +147,26 @@ def auto_cli(f, *args, **kwargs):
|
|
|
132
147
|
main = click.option(
|
|
133
148
|
"--log-level",
|
|
134
149
|
default="warning",
|
|
135
|
-
type=click.Choice(
|
|
136
|
-
["debug", "info", "warning", "error", "critical"], case_sensitive=False
|
|
137
|
-
),
|
|
150
|
+
type=click.Choice(["debug", "info", "warning", "error", "critical"], case_sensitive=False),
|
|
138
151
|
help="The logging level.",
|
|
139
152
|
)(main)
|
|
140
153
|
|
|
141
|
-
|
|
154
|
+
#
|
|
155
|
+
# Create Rich command
|
|
156
|
+
#
|
|
157
|
+
|
|
158
|
+
# show_metavars_column=False: the column types are not in a separate column
|
|
159
|
+
# append_metavars_help=True: the column types are shown below the help text
|
|
160
|
+
main = click.rich_config(
|
|
161
|
+
help_config=click.RichHelpConfiguration(
|
|
162
|
+
use_markdown=True,
|
|
163
|
+
show_metavars_column=False,
|
|
164
|
+
append_metavars_help=True,
|
|
165
|
+
footer_text=footer,
|
|
166
|
+
)
|
|
167
|
+
)(main)
|
|
168
|
+
|
|
169
|
+
# show_default=True: default values are shown in the help text
|
|
170
|
+
main = click.command(context_settings={"show_default": True}, help=help_text)(main)
|
|
171
|
+
|
|
172
|
+
main()
|
|
@@ -2,6 +2,7 @@ from .configuration import *
|
|
|
2
2
|
from .default_configuration import *
|
|
3
3
|
from .dict_configuration import *
|
|
4
4
|
from .merged_configuration import *
|
|
5
|
+
from .models import *
|
|
5
6
|
from .package_configuration import *
|
|
6
7
|
from .search_yaml_configuration import *
|
|
7
8
|
from .yaml_configuration import *
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from .models import Module
|
|
5
|
+
|
|
6
|
+
__all__ = ["Configuration"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Configuration(ABC):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
self._cached_config: Optional[Module] = None
|
|
12
|
+
|
|
13
|
+
def get_dict(self) -> Module:
|
|
14
|
+
if self._cached_config is None:
|
|
15
|
+
config = self._get_dict()
|
|
16
|
+
|
|
17
|
+
# validate the config
|
|
18
|
+
module = Module(**config)
|
|
19
|
+
|
|
20
|
+
self._cached_config = module
|
|
21
|
+
|
|
22
|
+
return self._cached_config
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def _get_dict(self) -> dict:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def is_empty(self) -> bool:
|
|
29
|
+
return self.get_dict() == {}
|
|
30
|
+
|
|
31
|
+
def __repr__(self) -> str:
|
|
32
|
+
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"):
|
|
@@ -6,7 +6,7 @@ from .dict_configuration import DictConfiguration
|
|
|
6
6
|
__all__ = ["MergedConfiguration"]
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def merge(*args):
|
|
9
|
+
def merge(*args: dict) -> dict:
|
|
10
10
|
assert len(args) > 0
|
|
11
11
|
|
|
12
12
|
first_entry = args[0]
|
|
@@ -41,4 +41,4 @@ def merge(*args):
|
|
|
41
41
|
|
|
42
42
|
class MergedConfiguration(DictConfiguration):
|
|
43
43
|
def __init__(self, *configs: Configuration):
|
|
44
|
-
super().__init__(merge(*[c.
|
|
44
|
+
super().__init__(merge(*[c._get_dict() for c in configs]))
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
from typing import Any, List, Optional, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, computed_field, model_validator
|
|
4
|
+
from stringcase import spinalcase
|
|
5
|
+
|
|
6
|
+
from ..polyfills import Literal
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Partner(BaseModel):
|
|
10
|
+
name: str
|
|
11
|
+
logo: str
|
|
12
|
+
url: Optional[str] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Author(BaseModel):
|
|
16
|
+
"""
|
|
17
|
+
Author information
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
first_name : str
|
|
21
|
+
First name of the author.
|
|
22
|
+
last_name : str
|
|
23
|
+
Last name of the author.
|
|
24
|
+
email : Optional[str]
|
|
25
|
+
Email of the author. If provided, the author is a corresponding author.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
first_name: str
|
|
29
|
+
last_name: str
|
|
30
|
+
email: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Publication(BaseModel):
|
|
34
|
+
title: str
|
|
35
|
+
authors: List[Author] = []
|
|
36
|
+
journal: str
|
|
37
|
+
year: int
|
|
38
|
+
doi: Optional[str]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class JobParameterChoice(BaseModel):
|
|
42
|
+
value: str
|
|
43
|
+
label: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class JobParameter(BaseModel):
|
|
47
|
+
name: str
|
|
48
|
+
type: str
|
|
49
|
+
visible_name: Optional[str] = None
|
|
50
|
+
help_text: Optional[str] = None
|
|
51
|
+
default: Any = None
|
|
52
|
+
required: bool = False
|
|
53
|
+
choices: Optional[List[JobParameterChoice]] = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Task = Literal[
|
|
57
|
+
"molecular_property_prediction",
|
|
58
|
+
"atom_property_prediction",
|
|
59
|
+
"derivative_property_prediction",
|
|
60
|
+
]
|
|
61
|
+
Level = Literal["molecule", "atom", "derivative"]
|
|
62
|
+
|
|
63
|
+
FormatSpec = Union[List[str], str]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class IncludeExcludeFormatSpec(BaseModel):
|
|
67
|
+
include: Optional[FormatSpec]
|
|
68
|
+
exclude: Optional[FormatSpec]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ResultProperty(BaseModel):
|
|
72
|
+
name: str
|
|
73
|
+
type: str
|
|
74
|
+
visible_name: Optional[str] = None
|
|
75
|
+
visible: bool = True
|
|
76
|
+
help_text: Optional[str] = None
|
|
77
|
+
sortable: bool = False
|
|
78
|
+
group: Optional[str] = None
|
|
79
|
+
level: Level = "molecule"
|
|
80
|
+
formats: Union[FormatSpec, IncludeExcludeFormatSpec, None] = None
|
|
81
|
+
representation: Optional[str] = None
|
|
82
|
+
from_property: Optional[str] = None
|
|
83
|
+
image_width: Optional[int] = None
|
|
84
|
+
image_height: Optional[int] = None
|
|
85
|
+
|
|
86
|
+
def is_visible(self, output_format: str) -> bool:
|
|
87
|
+
formats = self.formats
|
|
88
|
+
|
|
89
|
+
if formats is None:
|
|
90
|
+
return True
|
|
91
|
+
elif isinstance(formats, list):
|
|
92
|
+
return output_format in formats
|
|
93
|
+
elif isinstance(formats, IncludeExcludeFormatSpec):
|
|
94
|
+
include = formats.include
|
|
95
|
+
exclude = formats.exclude or []
|
|
96
|
+
return (include is None or output_format in include) and output_format not in exclude
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError(f"Invalid formats declaration {formats} in result property {self}")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Module(BaseModel):
|
|
102
|
+
@computed_field # type: ignore[prop-decorator]
|
|
103
|
+
@property
|
|
104
|
+
def id(self) -> str:
|
|
105
|
+
# TODO: incorporate versioning
|
|
106
|
+
# compute the primary key from name and version
|
|
107
|
+
# if "version" in module.keys():
|
|
108
|
+
# version = module["version"]
|
|
109
|
+
# else:
|
|
110
|
+
# version = "1.0.0"
|
|
111
|
+
# name = module["name"]
|
|
112
|
+
|
|
113
|
+
return spinalcase(self.name)
|
|
114
|
+
|
|
115
|
+
task: Optional[Task] = None
|
|
116
|
+
rank: Optional[int] = None
|
|
117
|
+
name: str
|
|
118
|
+
batch_size: int = 100
|
|
119
|
+
version: Optional[str] = None
|
|
120
|
+
visible_name: Optional[str] = None
|
|
121
|
+
visible: bool = True
|
|
122
|
+
logo: Optional[str] = None
|
|
123
|
+
logo_title: Optional[str] = None
|
|
124
|
+
logo_caption: Optional[str] = None
|
|
125
|
+
example_smiles: Optional[str] = None
|
|
126
|
+
title: Optional[str] = None
|
|
127
|
+
description: Optional[str] = None
|
|
128
|
+
partners: List[Partner] = []
|
|
129
|
+
publications: List[Publication] = []
|
|
130
|
+
about: Optional[str] = None
|
|
131
|
+
job_parameters: List[JobParameter] = []
|
|
132
|
+
result_properties: List[ResultProperty] = []
|
|
133
|
+
|
|
134
|
+
def get_property_columns_of_type(self, t: Level) -> List[ResultProperty]:
|
|
135
|
+
return [c for c in self.result_properties if c.level == t]
|
|
136
|
+
|
|
137
|
+
def molecular_property_columns(self) -> List[ResultProperty]:
|
|
138
|
+
return self.get_property_columns_of_type("molecule")
|
|
139
|
+
|
|
140
|
+
def atom_property_columns(self) -> List[ResultProperty]:
|
|
141
|
+
return self.get_property_columns_of_type("atom")
|
|
142
|
+
|
|
143
|
+
def derivative_property_columns(self) -> List[ResultProperty]:
|
|
144
|
+
return self.get_property_columns_of_type("derivative")
|
|
145
|
+
|
|
146
|
+
def get_visible_properties(self, output_format: str) -> List[ResultProperty]:
|
|
147
|
+
return [p for p in self.result_properties if p.is_visible(output_format)]
|
|
148
|
+
|
|
149
|
+
@model_validator(mode="after")
|
|
150
|
+
@classmethod
|
|
151
|
+
def validate_model(cls, values: Any) -> Any:
|
|
152
|
+
assert isinstance(values, Module)
|
|
153
|
+
|
|
154
|
+
num_atom_properties = len(values.get_property_columns_of_type("atom"))
|
|
155
|
+
num_derivative_properties = len(values.get_property_columns_of_type("derivative"))
|
|
156
|
+
task = values.task
|
|
157
|
+
if task is None:
|
|
158
|
+
# if task is not specified, try to derive it from the result_properties
|
|
159
|
+
if num_atom_properties > 0:
|
|
160
|
+
task = "atom_property_prediction"
|
|
161
|
+
elif num_derivative_properties > 0:
|
|
162
|
+
task = "derivative_property_prediction"
|
|
163
|
+
else:
|
|
164
|
+
task = "molecular_property_prediction"
|
|
165
|
+
|
|
166
|
+
values.task = task
|
|
167
|
+
else:
|
|
168
|
+
# if task is specified, check if it is consistent with the result_properties
|
|
169
|
+
if num_atom_properties > 0:
|
|
170
|
+
assert (
|
|
171
|
+
task == "atom_property_prediction"
|
|
172
|
+
), "Task should be atom_property_prediction if atom properties are present."
|
|
173
|
+
elif num_derivative_properties > 0:
|
|
174
|
+
assert task == "derivative_property_prediction", (
|
|
175
|
+
"Task should be derivative_property_prediction if derivative properties "
|
|
176
|
+
"are present."
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
assert task == "molecular_property_prediction", (
|
|
180
|
+
"Task should be molecular_property_prediction if no atom or derivative "
|
|
181
|
+
"properties are present."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# check that a module can only predict atom or derivative properties, not both
|
|
185
|
+
assert (
|
|
186
|
+
num_atom_properties == 0 or num_derivative_properties == 0
|
|
187
|
+
), "A module can only predict atom or derivative properties, not both."
|
|
188
|
+
|
|
189
|
+
# check that two properties with the same group appear next to each other
|
|
190
|
+
groups = [p.group for p in values.result_properties if p.group is not None]
|
|
191
|
+
for group in groups:
|
|
192
|
+
indices = [i for i, p in enumerate(values.result_properties) if p.group == group]
|
|
193
|
+
for i, j in zip(indices[:-1], indices[1:]):
|
|
194
|
+
assert i + 1 == j, (
|
|
195
|
+
f"Properties with the same group should appear next to each other, "
|
|
196
|
+
f"but group {group} appears at incides {i} and {j}."
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return values
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from ..polyfills import files
|
|
4
|
+
from .configuration import Configuration
|
|
5
|
+
from .dict_configuration import DictConfiguration
|
|
6
|
+
from .yaml_configuration import YamlConfiguration
|
|
7
|
+
|
|
8
|
+
__all__ = ["PackageConfiguration"]
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PackageConfiguration(Configuration):
|
|
14
|
+
def __init__(self, package: str, filename: str = "nerdd.yml") -> None:
|
|
15
|
+
super().__init__()
|
|
16
|
+
|
|
17
|
+
package_path = package.split(".")
|
|
18
|
+
package_root = package_path[0]
|
|
19
|
+
remaining_path = package_path[1:]
|
|
20
|
+
|
|
21
|
+
# get the resource directory
|
|
22
|
+
root_dir = files(package_root)
|
|
23
|
+
assert root_dir is not None
|
|
24
|
+
|
|
25
|
+
self.config: Configuration = DictConfiguration({})
|
|
26
|
+
|
|
27
|
+
# navigate to the config file
|
|
28
|
+
config_file = root_dir.joinpath(*remaining_path, filename)
|
|
29
|
+
assert config_file is not None and config_file.is_file()
|
|
30
|
+
|
|
31
|
+
logger.info(f"Found configuration file in package: {config_file}")
|
|
32
|
+
self.config = YamlConfiguration(
|
|
33
|
+
config_file.open(), base_path=root_dir.joinpath(*remaining_path)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def _get_dict(self) -> dict:
|
|
37
|
+
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
|
|
@@ -13,7 +12,9 @@ logger = logging.getLogger(__name__)
|
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class SearchYamlConfiguration(DictConfiguration):
|
|
16
|
-
def __init__(
|
|
15
|
+
def __init__(
|
|
16
|
+
self, start: str, base_path: Optional[str] = None, filename: str = "nerdd.yml"
|
|
17
|
+
) -> None:
|
|
17
18
|
# provide a default configuration if no configuration file is found
|
|
18
19
|
config: Configuration = DictConfiguration({})
|
|
19
20
|
|
|
@@ -23,8 +24,8 @@ class SearchYamlConfiguration(DictConfiguration):
|
|
|
23
24
|
# reached)
|
|
24
25
|
leaf = start
|
|
25
26
|
while True:
|
|
26
|
-
if os.path.isfile(os.path.join(leaf,
|
|
27
|
-
default_config_file = os.path.join(leaf,
|
|
27
|
+
if os.path.isfile(os.path.join(leaf, filename)):
|
|
28
|
+
default_config_file = os.path.join(leaf, filename)
|
|
28
29
|
break
|
|
29
30
|
elif leaf == os.path.dirname(leaf): # reached root
|
|
30
31
|
default_config_file = None
|
|
@@ -32,9 +33,7 @@ class SearchYamlConfiguration(DictConfiguration):
|
|
|
32
33
|
leaf = os.path.dirname(leaf)
|
|
33
34
|
|
|
34
35
|
if default_config_file is not None:
|
|
35
|
-
logger.info(
|
|
36
|
-
f"Found configuration file in project directory: {default_config_file}"
|
|
37
|
-
)
|
|
36
|
+
logger.info(f"Found configuration file in project directory: {default_config_file}")
|
|
38
37
|
config = YamlConfiguration(default_config_file, base_path)
|
|
39
38
|
|
|
40
|
-
super().__init__(config.
|
|
39
|
+
super().__init__(config._get_dict())
|