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.
Files changed (130) hide show
  1. nerdd_module-0.3.24/PKG-INFO +110 -0
  2. nerdd_module-0.3.24/README.md +19 -0
  3. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/__init__.py +2 -2
  4. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/cli.py +74 -43
  5. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/__init__.py +1 -0
  6. nerdd_module-0.3.24/nerdd_module/config/configuration.py +32 -0
  7. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/default_configuration.py +3 -1
  8. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/merged_configuration.py +2 -2
  9. nerdd_module-0.3.24/nerdd_module/config/models.py +199 -0
  10. nerdd_module-0.3.24/nerdd_module/config/package_configuration.py +37 -0
  11. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/search_yaml_configuration.py +8 -9
  12. nerdd_module-0.3.24/nerdd_module/config/yaml_configuration.py +98 -0
  13. nerdd_module-0.3.24/nerdd_module/converters/__init__.py +8 -0
  14. nerdd_module-0.3.24/nerdd_module/converters/basic_type_converter.py +23 -0
  15. nerdd_module-0.3.24/nerdd_module/converters/converter.py +109 -0
  16. nerdd_module-0.3.24/nerdd_module/converters/converter_config.py +15 -0
  17. nerdd_module-0.3.24/nerdd_module/converters/mol_converter.py +20 -0
  18. nerdd_module-0.3.24/nerdd_module/converters/problem_list_converter.py +39 -0
  19. nerdd_module-0.3.24/nerdd_module/converters/representation_converter.py +48 -0
  20. nerdd_module-0.3.24/nerdd_module/converters/source_list_converter.py +29 -0
  21. nerdd_module-0.3.24/nerdd_module/converters/void_converter.py +17 -0
  22. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/__init__.py +0 -1
  23. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/depth_first_explorer.py +16 -15
  24. nerdd_module-0.3.24/nerdd_module/input/explorer.py +13 -0
  25. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/file_reader.py +19 -13
  26. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/gzip_reader.py +4 -6
  27. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/inchi_reader.py +15 -10
  28. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/list_reader.py +4 -6
  29. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/mol_reader.py +5 -7
  30. nerdd_module-0.3.24/nerdd_module/input/reader.py +52 -0
  31. nerdd_module-0.3.24/nerdd_module/input/reader_config.py +9 -0
  32. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/sdf_reader.py +8 -11
  33. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/smiles_reader.py +12 -13
  34. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/string_reader.py +4 -6
  35. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/tar_reader.py +5 -7
  36. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/input/zip_reader.py +5 -7
  37. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/__init__.py +2 -1
  38. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/assign_mol_id_step.py +4 -2
  39. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/assign_name_step.py +2 -4
  40. nerdd_module-0.3.24/nerdd_module/model/convert_representations_step.py +26 -0
  41. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/enforce_schema_step.py +8 -7
  42. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/model.py +45 -64
  43. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/read_input_step.py +5 -5
  44. nerdd_module-0.3.24/nerdd_module/model/simple_model.py +217 -0
  45. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/model/write_output_step.py +6 -3
  46. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/__init__.py +2 -1
  47. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/csv_writer.py +2 -4
  48. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/file_writer.py +5 -5
  49. nerdd_module-0.3.24/nerdd_module/output/iterator_writer.py +13 -0
  50. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/pandas_writer.py +4 -4
  51. nerdd_module-0.3.24/nerdd_module/output/record_list_writer.py +13 -0
  52. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/output/sdf_writer.py +14 -7
  53. nerdd_module-0.3.24/nerdd_module/output/writer.py +58 -0
  54. nerdd_module-0.3.24/nerdd_module/polyfills/__init__.py +7 -0
  55. nerdd_module-0.3.24/nerdd_module/polyfills/block_logs.py +46 -0
  56. nerdd_module-0.3.24/nerdd_module/polyfills/files.py +13 -0
  57. nerdd_module-0.3.24/nerdd_module/polyfills/get_entry_points.py +27 -0
  58. nerdd_module-0.3.24/nerdd_module/polyfills/literal.py +8 -0
  59. nerdd_module-0.3.24/nerdd_module/polyfills/typed_dict.py +8 -0
  60. nerdd_module-0.3.24/nerdd_module/polyfills/types.py +14 -0
  61. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/check_valid_smiles.py +1 -1
  62. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/chembl_structure_pipeline.py +14 -15
  63. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/filter_by_element.py +6 -16
  64. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/filter_by_weight.py +8 -11
  65. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/preprocessing_step.py +5 -5
  66. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/remove_stereochemistry.py +2 -4
  67. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/sanitize.py +4 -3
  68. nerdd_module-0.3.24/nerdd_module/problem.py +42 -0
  69. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/steps/map_step.py +6 -6
  70. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/steps/step.py +1 -1
  71. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/__init__.py +1 -1
  72. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/checks.py +31 -54
  73. nerdd_module-0.3.3/tests/steps/input.py → nerdd_module-0.3.24/nerdd_module/tests/files.py +13 -53
  74. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/models/AtomicMassModel.py +22 -5
  75. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/models/MolWeightModel.py +19 -11
  76. nerdd_module-0.3.24/nerdd_module/tests/predictions.py +58 -0
  77. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/representations.py +14 -11
  78. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/util/__init__.py +0 -1
  79. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/util/call_with_mappings.py +6 -5
  80. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/util/package.py +2 -1
  81. nerdd_module-0.3.24/nerdd_module.egg-info/PKG-INFO +110 -0
  82. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module.egg-info/SOURCES.txt +18 -15
  83. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module.egg-info/requires.txt +16 -6
  84. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module.egg-info/top_level.txt +0 -1
  85. nerdd_module-0.3.24/pyproject.toml +151 -0
  86. nerdd_module-0.3.3/PKG-INFO +0 -77
  87. nerdd_module-0.3.3/README.md +0 -18
  88. nerdd_module-0.3.3/nerdd_module/config/configuration.py +0 -71
  89. nerdd_module-0.3.3/nerdd_module/config/package_configuration.py +0 -36
  90. nerdd_module-0.3.3/nerdd_module/config/yaml_configuration.py +0 -60
  91. nerdd_module-0.3.3/nerdd_module/converters/__init__.py +0 -2
  92. nerdd_module-0.3.3/nerdd_module/converters/converter.py +0 -16
  93. nerdd_module-0.3.3/nerdd_module/converters/converter_registry.py +0 -61
  94. nerdd_module-0.3.3/nerdd_module/converters/identity_converter.py +0 -5
  95. nerdd_module-0.3.3/nerdd_module/input/explorer.py +0 -16
  96. nerdd_module-0.3.3/nerdd_module/input/reader.py +0 -25
  97. nerdd_module-0.3.3/nerdd_module/input/reader_registry.py +0 -41
  98. nerdd_module-0.3.3/nerdd_module/model/add_smiles_step.py +0 -25
  99. nerdd_module-0.3.3/nerdd_module/model/simple_model.py +0 -158
  100. nerdd_module-0.3.3/nerdd_module/output/iterator_writer.py +0 -13
  101. nerdd_module-0.3.3/nerdd_module/output/record_list_writer.py +0 -13
  102. nerdd_module-0.3.3/nerdd_module/output/writer.py +0 -18
  103. nerdd_module-0.3.3/nerdd_module/output/writer_registry.py +0 -50
  104. nerdd_module-0.3.3/nerdd_module/polyfills/__init__.py +0 -3
  105. nerdd_module-0.3.3/nerdd_module/polyfills/files.py +0 -8
  106. nerdd_module-0.3.3/nerdd_module/polyfills/get_entry_points.py +0 -18
  107. nerdd_module-0.3.3/nerdd_module/problem.py +0 -17
  108. nerdd_module-0.3.3/nerdd_module/tests/predictions.py +0 -10
  109. nerdd_module-0.3.3/nerdd_module/tests/predictors.py +0 -42
  110. nerdd_module-0.3.3/nerdd_module/util/class_decorator.py +0 -29
  111. nerdd_module-0.3.3/nerdd_module.egg-info/PKG-INFO +0 -77
  112. nerdd_module-0.3.3/setup.py +0 -92
  113. nerdd_module-0.3.3/tests/conftest.py +0 -7
  114. nerdd_module-0.3.3/tests/steps/__init__.py +0 -3
  115. nerdd_module-0.3.3/tests/steps/checks.py +0 -77
  116. nerdd_module-0.3.3/tests/steps/preprocessing.py +0 -111
  117. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/LICENSE +0 -0
  118. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/config/dict_configuration.py +0 -0
  119. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/polyfills/version.py +0 -0
  120. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/preprocessing/__init__.py +0 -0
  121. /nerdd_module-0.3.3/tests/__init__.py → /nerdd_module-0.3.24/nerdd_module/py.typed +0 -0
  122. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/steps/__init__.py +0 -0
  123. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/steps/output_step.py +0 -0
  124. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/models/__init__.py +0 -0
  125. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/preprocessing/DummyPreprocessingStep.py +0 -0
  126. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/tests/preprocessing/__init__.py +0 -0
  127. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module/version.py +0 -0
  128. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/nerdd_module.egg-info/dependency_links.txt +0 -0
  129. {nerdd_module-0.3.3 → nerdd_module-0.3.24}/setup.cfg +0 -0
  130. {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,7 +1,7 @@
1
1
  from .cli import *
2
- from .input import ReaderRegistry
2
+ from .converters import *
3
3
  from .model import *
4
- from .output import WriterRegistry
4
+ from .output import *
5
5
  from .polyfills import get_entry_points
6
6
  from .problem import *
7
7
  from .version import *
@@ -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 # type: ignore
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 "choices" in param:
24
- choices = [c["value"] for c in param["choices"]]
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": float,
29
- "int": int,
30
- "str": str,
31
- "bool": bool,
34
+ "float": click.FLOAT,
35
+ "integer": click.INT,
36
+ "string": click.STRING,
37
+ "bool": click.BOOL,
32
38
  }
33
39
 
34
- return type_map[param.get("type")]
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 = ["sdf", "csv"]
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 "example_smiles" in config:
61
- examples.append(config["example_smiles"])
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
- # show_default=True: default values are shown in the help text
71
- # show_metavars_column=False: the column types are not in a separate column
72
- # append_metavars_help=True: the column types are shown below the help text
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 config.get("job_parameters", []):
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["name"])
122
+ param_name = spinalcase(param.name)
108
123
  main = click.option(
109
124
  f"--{param_name}",
110
- default=param.get("default", None),
125
+ default=param.default,
111
126
  type=infer_click_type(param),
112
- help=param.get("help_text", None),
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
- return main()
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.get_dict() for c in configs]))
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 sys
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__(self, start: str, base_path: Optional[str] = None) -> None:
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, "nerdd.yml")):
27
- default_config_file = os.path.join(leaf, "nerdd.yml")
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.get_dict())
39
+ super().__init__(config._get_dict())