acellera-openff-utilities 0.1.16__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.
- acellera_openff_utilities-0.1.16/LICENSE +22 -0
- acellera_openff_utilities-0.1.16/MANIFEST.in +6 -0
- acellera_openff_utilities-0.1.16/PKG-INFO +28 -0
- acellera_openff_utilities-0.1.16/README.md +18 -0
- acellera_openff_utilities-0.1.16/acellera_openff_utilities.egg-info/PKG-INFO +28 -0
- acellera_openff_utilities-0.1.16/acellera_openff_utilities.egg-info/SOURCES.txt +25 -0
- acellera_openff_utilities-0.1.16/acellera_openff_utilities.egg-info/dependency_links.txt +1 -0
- acellera_openff_utilities-0.1.16/acellera_openff_utilities.egg-info/top_level.txt +3 -0
- acellera_openff_utilities-0.1.16/devtools/scripts/assert_openeye_not_found.py +6 -0
- acellera_openff_utilities-0.1.16/docs/conf.py +173 -0
- acellera_openff_utilities-0.1.16/openff/utilities/__init__.py +30 -0
- acellera_openff_utilities-0.1.16/openff/utilities/_tests/__init__.py +0 -0
- acellera_openff_utilities-0.1.16/openff/utilities/_tests/test_exceptions.py +17 -0
- acellera_openff_utilities-0.1.16/openff/utilities/_tests/test_provenance.py +54 -0
- acellera_openff_utilities-0.1.16/openff/utilities/_tests/test_star_import.py +1 -0
- acellera_openff_utilities-0.1.16/openff/utilities/_tests/test_testing_utilities.py +16 -0
- acellera_openff_utilities-0.1.16/openff/utilities/_tests/test_utilities.py +181 -0
- acellera_openff_utilities-0.1.16/openff/utilities/data/data.dat +2 -0
- acellera_openff_utilities-0.1.16/openff/utilities/data/more/more.dat +0 -0
- acellera_openff_utilities-0.1.16/openff/utilities/exceptions.py +52 -0
- acellera_openff_utilities-0.1.16/openff/utilities/provenance.py +66 -0
- acellera_openff_utilities-0.1.16/openff/utilities/py.typed +0 -0
- acellera_openff_utilities-0.1.16/openff/utilities/testing.py +54 -0
- acellera_openff_utilities-0.1.16/openff/utilities/utilities.py +261 -0
- acellera_openff_utilities-0.1.16/openff/utilities/warnings.py +4 -0
- acellera_openff_utilities-0.1.16/pyproject.toml +85 -0
- acellera_openff_utilities-0.1.16/setup.cfg +4 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2021 The Open Force Field Initiative
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: acellera-openff-utilities
|
|
3
|
+
Version: 0.1.16
|
|
4
|
+
Summary: A collection of miscellaneous utility functions used throughout the OpenFF stack
|
|
5
|
+
Author-email: Open Force Field Initiative <info@openforcefield.org>
|
|
6
|
+
License: MIT
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
|
|
11
|
+
openff-utilities
|
|
12
|
+
==============================
|
|
13
|
+
[//]: # (Badges)
|
|
14
|
+
[](https://github.com/openforcefield/openff-utilities/actions?query=workflow%3ACI)
|
|
15
|
+
[](https://codecov.io/gh/openforcefield/openff-utilities/branch/main)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
A collection of miscellaneous utility functions used throughout the OpenFF stack
|
|
19
|
+
|
|
20
|
+
### Copyright
|
|
21
|
+
|
|
22
|
+
Copyright (c) 2021, The Open Force Field Initiative
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#### Acknowledgements
|
|
26
|
+
|
|
27
|
+
Project based on the
|
|
28
|
+
[Computational Molecular Science Python Cookiecutter](https://github.com/molssi/cookiecutter-cms) version 1.5.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
openff-utilities
|
|
2
|
+
==============================
|
|
3
|
+
[//]: # (Badges)
|
|
4
|
+
[](https://github.com/openforcefield/openff-utilities/actions?query=workflow%3ACI)
|
|
5
|
+
[](https://codecov.io/gh/openforcefield/openff-utilities/branch/main)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
A collection of miscellaneous utility functions used throughout the OpenFF stack
|
|
9
|
+
|
|
10
|
+
### Copyright
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2021, The Open Force Field Initiative
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#### Acknowledgements
|
|
16
|
+
|
|
17
|
+
Project based on the
|
|
18
|
+
[Computational Molecular Science Python Cookiecutter](https://github.com/molssi/cookiecutter-cms) version 1.5.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: acellera-openff-utilities
|
|
3
|
+
Version: 0.1.16
|
|
4
|
+
Summary: A collection of miscellaneous utility functions used throughout the OpenFF stack
|
|
5
|
+
Author-email: Open Force Field Initiative <info@openforcefield.org>
|
|
6
|
+
License: MIT
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
|
|
11
|
+
openff-utilities
|
|
12
|
+
==============================
|
|
13
|
+
[//]: # (Badges)
|
|
14
|
+
[](https://github.com/openforcefield/openff-utilities/actions?query=workflow%3ACI)
|
|
15
|
+
[](https://codecov.io/gh/openforcefield/openff-utilities/branch/main)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
A collection of miscellaneous utility functions used throughout the OpenFF stack
|
|
19
|
+
|
|
20
|
+
### Copyright
|
|
21
|
+
|
|
22
|
+
Copyright (c) 2021, The Open Force Field Initiative
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#### Acknowledgements
|
|
26
|
+
|
|
27
|
+
Project based on the
|
|
28
|
+
[Computational Molecular Science Python Cookiecutter](https://github.com/molssi/cookiecutter-cms) version 1.5.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
acellera_openff_utilities.egg-info/PKG-INFO
|
|
6
|
+
acellera_openff_utilities.egg-info/SOURCES.txt
|
|
7
|
+
acellera_openff_utilities.egg-info/dependency_links.txt
|
|
8
|
+
acellera_openff_utilities.egg-info/top_level.txt
|
|
9
|
+
devtools/scripts/assert_openeye_not_found.py
|
|
10
|
+
docs/conf.py
|
|
11
|
+
openff/utilities/__init__.py
|
|
12
|
+
openff/utilities/exceptions.py
|
|
13
|
+
openff/utilities/provenance.py
|
|
14
|
+
openff/utilities/py.typed
|
|
15
|
+
openff/utilities/testing.py
|
|
16
|
+
openff/utilities/utilities.py
|
|
17
|
+
openff/utilities/warnings.py
|
|
18
|
+
openff/utilities/_tests/__init__.py
|
|
19
|
+
openff/utilities/_tests/test_exceptions.py
|
|
20
|
+
openff/utilities/_tests/test_provenance.py
|
|
21
|
+
openff/utilities/_tests/test_star_import.py
|
|
22
|
+
openff/utilities/_tests/test_testing_utilities.py
|
|
23
|
+
openff/utilities/_tests/test_utilities.py
|
|
24
|
+
openff/utilities/data/data.dat
|
|
25
|
+
openff/utilities/data/more/more.dat
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Configuration file for the Sphinx documentation builder.
|
|
3
|
+
#
|
|
4
|
+
# This file does only contain a selection of the most common options. For a
|
|
5
|
+
# full list see the documentation:
|
|
6
|
+
# http://www.sphinx-doc.org/en/stable/config
|
|
7
|
+
|
|
8
|
+
# -- Path setup --------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
# If extensions (or modules to document with autodoc) are in another directory,
|
|
11
|
+
# add these directories to sys.path here. If the directory is relative to the
|
|
12
|
+
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
13
|
+
|
|
14
|
+
# Incase the project was not installed
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
sys.path.insert(0, os.path.abspath(".."))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# -- Project information -----------------------------------------------------
|
|
22
|
+
|
|
23
|
+
project = "openff-utilities"
|
|
24
|
+
copyright = (
|
|
25
|
+
"2021, The Open Force Field Initiative. Project structure based on the "
|
|
26
|
+
"Computational Molecular Science Python Cookiecutter version 1.5"
|
|
27
|
+
)
|
|
28
|
+
author = "The Open Force Field Initiative"
|
|
29
|
+
|
|
30
|
+
# The short X.Y version
|
|
31
|
+
version = ""
|
|
32
|
+
# The full version, including alpha/beta/rc tags
|
|
33
|
+
release = ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# -- General configuration ---------------------------------------------------
|
|
37
|
+
|
|
38
|
+
# If your documentation needs a minimal Sphinx version, state it here.
|
|
39
|
+
#
|
|
40
|
+
# needs_sphinx = '1.0'
|
|
41
|
+
|
|
42
|
+
# Add any Sphinx extension module names here, as strings. They can be
|
|
43
|
+
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
44
|
+
# ones.
|
|
45
|
+
extensions = [
|
|
46
|
+
"sphinx.ext.autosummary",
|
|
47
|
+
"sphinx.ext.autodoc",
|
|
48
|
+
"sphinx.ext.mathjax",
|
|
49
|
+
"sphinx.ext.viewcode",
|
|
50
|
+
"sphinx.ext.napoleon",
|
|
51
|
+
"sphinx.ext.intersphinx",
|
|
52
|
+
"sphinx.ext.extlinks",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
autosummary_generate = True
|
|
56
|
+
napoleon_google_docstring = False
|
|
57
|
+
napoleon_use_param = False
|
|
58
|
+
napoleon_use_ivar = True
|
|
59
|
+
|
|
60
|
+
# Add any paths that contain templates here, relative to this directory.
|
|
61
|
+
templates_path = ["_templates"]
|
|
62
|
+
|
|
63
|
+
# The suffix(es) of source filenames.
|
|
64
|
+
# You can specify multiple suffix as a list of string:
|
|
65
|
+
#
|
|
66
|
+
# source_suffix = ['.rst', '.md']
|
|
67
|
+
source_suffix = ".rst"
|
|
68
|
+
|
|
69
|
+
# The master toctree document.
|
|
70
|
+
master_doc = "index"
|
|
71
|
+
|
|
72
|
+
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
73
|
+
# for a list of supported languages.
|
|
74
|
+
#
|
|
75
|
+
# This is also used if you do content translation via gettext catalogs.
|
|
76
|
+
# Usually you set "language" from the command line for these cases.
|
|
77
|
+
language = None
|
|
78
|
+
|
|
79
|
+
# List of patterns, relative to source directory, that match files and
|
|
80
|
+
# directories to ignore when looking for source files.
|
|
81
|
+
# This pattern also affects html_static_path and html_extra_path .
|
|
82
|
+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
|
83
|
+
|
|
84
|
+
# The name of the Pygments (syntax highlighting) style to use.
|
|
85
|
+
pygments_style = "default"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# -- Options for HTML output -------------------------------------------------
|
|
89
|
+
|
|
90
|
+
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
91
|
+
# a list of builtin themes.
|
|
92
|
+
#
|
|
93
|
+
html_theme = "sphinx_rtd_theme"
|
|
94
|
+
|
|
95
|
+
# Theme options are theme-specific and customize the look and feel of a theme
|
|
96
|
+
# further. For a list of options available for each theme, see the
|
|
97
|
+
# documentation.
|
|
98
|
+
#
|
|
99
|
+
# html_theme_options = {}
|
|
100
|
+
|
|
101
|
+
# Add any paths that contain custom static files (such as style sheets) here,
|
|
102
|
+
# relative to this directory. They are copied after the builtin static files,
|
|
103
|
+
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
104
|
+
html_static_path = ["_static"]
|
|
105
|
+
|
|
106
|
+
# Custom sidebar templates, must be a dictionary that maps document names
|
|
107
|
+
# to template names.
|
|
108
|
+
#
|
|
109
|
+
# The default sidebars (for documents that don't match any pattern) are
|
|
110
|
+
# defined by theme itself. Builtin themes are using these templates by
|
|
111
|
+
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
|
112
|
+
# 'searchbox.html']``.
|
|
113
|
+
#
|
|
114
|
+
# html_sidebars = {}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# -- Options for HTMLHelp output ---------------------------------------------
|
|
118
|
+
|
|
119
|
+
# Output file base name for HTML help builder.
|
|
120
|
+
htmlhelp_basename = "openffdoc"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# -- Options for LaTeX output ------------------------------------------------
|
|
124
|
+
|
|
125
|
+
latex_elements = {
|
|
126
|
+
# The paper size ('letterpaper' or 'a4paper').
|
|
127
|
+
#
|
|
128
|
+
# 'papersize': 'letterpaper',
|
|
129
|
+
# The font size ('10pt', '11pt' or '12pt').
|
|
130
|
+
#
|
|
131
|
+
# 'pointsize': '10pt',
|
|
132
|
+
# Additional stuff for the LaTeX preamble.
|
|
133
|
+
#
|
|
134
|
+
# 'preamble': '',
|
|
135
|
+
# Latex figure (float) alignment
|
|
136
|
+
#
|
|
137
|
+
# 'figure_align': 'htbp',
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Grouping the document tree into LaTeX files. List of tuples
|
|
141
|
+
# (source start file, target name, title,
|
|
142
|
+
# author, documentclass [howto, manual, or own class]).
|
|
143
|
+
latex_documents = [
|
|
144
|
+
(master_doc, "openff.tex", "openff-utilities Documentation", "openff", "manual"),
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# -- Options for manual page output ------------------------------------------
|
|
149
|
+
|
|
150
|
+
# One entry per manual page. List of tuples
|
|
151
|
+
# (source start file, name, description, authors, manual section).
|
|
152
|
+
man_pages = [(master_doc, "openff", "openff-utilities Documentation", [author], 1)]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# -- Options for Texinfo output ----------------------------------------------
|
|
156
|
+
|
|
157
|
+
# Grouping the document tree into Texinfo files. List of tuples
|
|
158
|
+
# (source start file, target name, title, author,
|
|
159
|
+
# dir menu entry, description, category)
|
|
160
|
+
texinfo_documents = [
|
|
161
|
+
(
|
|
162
|
+
master_doc,
|
|
163
|
+
"openff",
|
|
164
|
+
"openff-utilities Documentation",
|
|
165
|
+
author,
|
|
166
|
+
"openff",
|
|
167
|
+
"A collection of miscellaneous utility functions used throughout the OpenFF stack",
|
|
168
|
+
"Miscellaneous",
|
|
169
|
+
),
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# -- Extension configuration -------------------------------------------------
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
3
|
+
from openff.utilities.exceptions import MissingOptionalDependencyError
|
|
4
|
+
from openff.utilities.provenance import get_ambertools_version
|
|
5
|
+
from openff.utilities.testing import skip_if_missing, skip_if_missing_exec
|
|
6
|
+
from openff.utilities.utilities import (
|
|
7
|
+
get_data_dir_path,
|
|
8
|
+
get_data_file_path,
|
|
9
|
+
has_executable,
|
|
10
|
+
has_package,
|
|
11
|
+
requires_oe_module,
|
|
12
|
+
requires_package,
|
|
13
|
+
temporary_cd,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = (
|
|
17
|
+
"MissingOptionalDependencyError",
|
|
18
|
+
"get_ambertools_version",
|
|
19
|
+
"get_data_dir_path",
|
|
20
|
+
"get_data_file_path",
|
|
21
|
+
"has_executable",
|
|
22
|
+
"has_package",
|
|
23
|
+
"requires_oe_module",
|
|
24
|
+
"requires_package",
|
|
25
|
+
"skip_if_missing",
|
|
26
|
+
"skip_if_missing_exec",
|
|
27
|
+
"temporary_cd",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__version__ = version("openff.utilities")
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from openff.utilities.exceptions import MissingOptionalDependencyError, OpenFFError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_exceptions():
|
|
7
|
+
with pytest.raises(
|
|
8
|
+
MissingOptionalDependencyError,
|
|
9
|
+
match="The required foobar module could not be imported.*Try installing.*conda-forge.*",
|
|
10
|
+
):
|
|
11
|
+
raise MissingOptionalDependencyError(library_name="foobar")
|
|
12
|
+
|
|
13
|
+
with pytest.raises(MissingOptionalDependencyError, match=".*barbaz.*missing license."):
|
|
14
|
+
raise MissingOptionalDependencyError(library_name="barbaz", license_issue=True)
|
|
15
|
+
|
|
16
|
+
with pytest.raises(OpenFFError):
|
|
17
|
+
raise MissingOptionalDependencyError("numpy")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from openff.utilities.provenance import (
|
|
6
|
+
_get_conda_list_package_versions,
|
|
7
|
+
get_ambertools_version,
|
|
8
|
+
)
|
|
9
|
+
from openff.utilities.warnings import CondaExecutableNotFoundWarning
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def mock_has_executable(executable_name: str) -> bool:
|
|
13
|
+
return False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.leaky
|
|
17
|
+
def test_conda_unavailable_returns_empty_dict(monkeypatch):
|
|
18
|
+
"""
|
|
19
|
+
Test that if `conda|etc. list` fails, with or without AmberTools (properly) installed,
|
|
20
|
+
get_ambertools_version returns `None`.
|
|
21
|
+
|
|
22
|
+
This test leaks! Do not run it alongside other tests! Select either it "pytest -m leaky"
|
|
23
|
+
or avoid it "pytest -m 'not leaky'"
|
|
24
|
+
"""
|
|
25
|
+
with (
|
|
26
|
+
monkeypatch.context() as m,
|
|
27
|
+
pytest.warns(CondaExecutableNotFoundWarning, match="No .* executable found"),
|
|
28
|
+
):
|
|
29
|
+
m.setenv("PIXI_IN_SHELL", "0")
|
|
30
|
+
m.setenv("CONDA_SHLVL", "0")
|
|
31
|
+
|
|
32
|
+
assert _get_conda_list_package_versions() == dict()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_conda_available_get_ambertools_version_found():
|
|
36
|
+
# Skip if ambertools is not installed - sander would a better import test
|
|
37
|
+
# for AmberTools (since parmed could come from the standalone
|
|
38
|
+
# parmed package), however it's currently broken on ARM macs.
|
|
39
|
+
pytest.importorskip("parmed")
|
|
40
|
+
|
|
41
|
+
assert get_ambertools_version() is not None, (
|
|
42
|
+
f"note that len of package versions is {len(_get_conda_list_package_versions())}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_conda_available_get_ambertools_version_not_found():
|
|
47
|
+
try:
|
|
48
|
+
importlib.import_module("parmed")
|
|
49
|
+
except ImportError:
|
|
50
|
+
assert len(_get_conda_list_package_versions()) > 0
|
|
51
|
+
assert get_ambertools_version() is None
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
pytest.skip("only run when ambertools is not installed.")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from openff.utilities import * # noqa: F403
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from openff.utilities.testing import skip_if_missing, skip_if_missing_exec
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_skips():
|
|
7
|
+
assert not skip_if_missing("pip").args[0]
|
|
8
|
+
assert skip_if_missing("numpynumpy").args[0]
|
|
9
|
+
|
|
10
|
+
assert not skip_if_missing_exec("python").args[0]
|
|
11
|
+
assert skip_if_missing_exec("python4").args[0]
|
|
12
|
+
assert not skip_if_missing_exec(["python", "python4"]).args[0]
|
|
13
|
+
assert skip_if_missing_exec(["python4", "python5"]).args[0]
|
|
14
|
+
|
|
15
|
+
with pytest.raises(ValueError, match="Bad type.*int"):
|
|
16
|
+
skip_if_missing_exec(0)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from openff.utilities.exceptions import MissingOptionalDependencyError
|
|
6
|
+
from openff.utilities.testing import skip_if_missing
|
|
7
|
+
from openff.utilities.utilities import (
|
|
8
|
+
get_data_dir_path,
|
|
9
|
+
get_data_file_path,
|
|
10
|
+
has_executable,
|
|
11
|
+
has_package,
|
|
12
|
+
requires_oe_module,
|
|
13
|
+
requires_package,
|
|
14
|
+
temporary_cd,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def compare_paths(path_1: str, path_2: str) -> bool:
|
|
19
|
+
"""Checks whether two paths are the same.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
path_1
|
|
24
|
+
The first path.
|
|
25
|
+
path_2
|
|
26
|
+
The second path.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
True if the paths are equivalent.
|
|
31
|
+
"""
|
|
32
|
+
return os.path.normpath(path_1) == os.path.normpath(path_2)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_get_data_dir_path():
|
|
36
|
+
"""Tests that the `get_data_dir_path` can correctly find
|
|
37
|
+
data directories.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# Test a path which should exist.
|
|
41
|
+
data_file_path = get_data_dir_path("data/more", package_name="openff.utilities")
|
|
42
|
+
assert os.path.isdir(data_file_path)
|
|
43
|
+
|
|
44
|
+
# Ensure a double-checking through data/ takes place
|
|
45
|
+
data_file_path = get_data_dir_path("more", package_name="openff.utilities")
|
|
46
|
+
assert os.path.isdir(data_file_path)
|
|
47
|
+
|
|
48
|
+
# Test a path which should not exist.
|
|
49
|
+
with pytest.raises(NotADirectoryError):
|
|
50
|
+
get_data_dir_path("missing", package_name="openff.utilities")
|
|
51
|
+
|
|
52
|
+
# Test that a file directory raises NotaDirectoryError
|
|
53
|
+
with pytest.raises(NotADirectoryError):
|
|
54
|
+
get_data_dir_path("data/data.dat", package_name="openff.utilities")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_get_data_file_path():
|
|
58
|
+
"""Tests that the `get_data_file_path` can correctly find
|
|
59
|
+
data files.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# Test a path which should exist.
|
|
63
|
+
data_file_path = get_data_file_path("data/data.dat", package_name="openff.utilities")
|
|
64
|
+
assert os.path.isfile(data_file_path)
|
|
65
|
+
|
|
66
|
+
# Ensure a double-checking through data/ takes place
|
|
67
|
+
data_file_path = get_data_file_path("data.dat", package_name="openff.utilities")
|
|
68
|
+
assert os.path.isfile(data_file_path)
|
|
69
|
+
|
|
70
|
+
# Test a path which should not exist.
|
|
71
|
+
with pytest.raises(FileNotFoundError):
|
|
72
|
+
get_data_file_path("missing.file", package_name="openff.utilities")
|
|
73
|
+
|
|
74
|
+
# Test that a directory raises FileNotFoundError
|
|
75
|
+
with pytest.raises(FileNotFoundError):
|
|
76
|
+
get_data_file_path("data/", package_name="openff.utilities")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_temporary_cd():
|
|
80
|
+
"""Tests that temporary cd works as expected"""
|
|
81
|
+
|
|
82
|
+
original_directory = os.getcwd()
|
|
83
|
+
|
|
84
|
+
# Move to the parent directory
|
|
85
|
+
with temporary_cd(os.pardir):
|
|
86
|
+
current_directory = os.getcwd()
|
|
87
|
+
expected_directory = os.path.abspath(os.path.join(original_directory, os.pardir))
|
|
88
|
+
|
|
89
|
+
assert compare_paths(current_directory, expected_directory)
|
|
90
|
+
|
|
91
|
+
assert compare_paths(os.getcwd(), original_directory)
|
|
92
|
+
|
|
93
|
+
# Move to a temporary directory
|
|
94
|
+
with temporary_cd():
|
|
95
|
+
assert not compare_paths(os.getcwd(), original_directory)
|
|
96
|
+
|
|
97
|
+
assert compare_paths(os.getcwd(), original_directory)
|
|
98
|
+
|
|
99
|
+
# Move to the same directory
|
|
100
|
+
with temporary_cd(""):
|
|
101
|
+
assert compare_paths(os.getcwd(), original_directory)
|
|
102
|
+
|
|
103
|
+
assert compare_paths(os.getcwd(), original_directory)
|
|
104
|
+
|
|
105
|
+
with temporary_cd(os.curdir):
|
|
106
|
+
assert compare_paths(os.getcwd(), original_directory)
|
|
107
|
+
|
|
108
|
+
assert compare_paths(os.getcwd(), original_directory)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_has_package():
|
|
112
|
+
assert has_package("os")
|
|
113
|
+
assert has_package("pytest")
|
|
114
|
+
assert not has_package("nummmmmmpy")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_has_executable():
|
|
118
|
+
assert has_executable("pwd")
|
|
119
|
+
assert has_executable("pytest")
|
|
120
|
+
|
|
121
|
+
assert has_executable("/usr/bin/whoami")
|
|
122
|
+
|
|
123
|
+
assert not has_package("pyyyyython")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_requires_package():
|
|
127
|
+
"""Tests that the ``requires_package`` utility behaves as expected."""
|
|
128
|
+
|
|
129
|
+
def dummy_function():
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
# sys should always be found so this should not raise an exception.
|
|
133
|
+
requires_package("sys")(dummy_function)()
|
|
134
|
+
|
|
135
|
+
with pytest.raises(MissingOptionalDependencyError) as error_info:
|
|
136
|
+
requires_package("fake-lib")(dummy_function)()
|
|
137
|
+
|
|
138
|
+
assert error_info.value.library_name == "fake-lib"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@skip_if_missing("openeye.oechem")
|
|
142
|
+
@pytest.mark.skipif("OE_LICENSE" not in os.environ, reason="Requires an OpenEye license is NOT set up")
|
|
143
|
+
def test_requires_oe_module():
|
|
144
|
+
"""Tests that the ``requires_package`` utility behaves as expected when an OpenEye license is set up."""
|
|
145
|
+
|
|
146
|
+
def dummy_function():
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
requires_oe_module("oechem")(dummy_function)()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@pytest.mark.parametrize("oe_module", ["oechem", "oeiupac", "oeomega", "oequacpac", "oedepict"])
|
|
153
|
+
@skip_if_missing("openeye")
|
|
154
|
+
@pytest.mark.skipif("OE_LICENSE" in os.environ, reason="Requires an OpenEye license is NOT set up")
|
|
155
|
+
def test_requires_oe_module_installed_missing_license(oe_module):
|
|
156
|
+
"""Tests that the ``requires_package`` utility behaves as expected while OpenEye toolkits are
|
|
157
|
+
installed but no OpenEye license is set up."""
|
|
158
|
+
|
|
159
|
+
def dummy_function():
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
with pytest.raises(MissingOptionalDependencyError) as error_info:
|
|
163
|
+
requires_oe_module(oe_module)(dummy_function)()
|
|
164
|
+
|
|
165
|
+
assert oe_module in str(error_info.value)
|
|
166
|
+
assert "conda-forge" not in str(error_info.value)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@pytest.mark.skipif(has_package("openeye.oechem"), reason="Requires OpenEye toolkits are NOT installed")
|
|
170
|
+
def test_requires_oe_module_not_installed():
|
|
171
|
+
"""Tests that the ``requires_package`` utility behaves as expected while OpenEye toolkits are
|
|
172
|
+
installed."""
|
|
173
|
+
|
|
174
|
+
def dummy_function():
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
with pytest.raises(MissingOptionalDependencyError) as error_info:
|
|
178
|
+
requires_oe_module("oechem")(dummy_function)()
|
|
179
|
+
|
|
180
|
+
assert "oechem" in str(error_info.value)
|
|
181
|
+
assert "conda-forge" not in str(error_info.value)
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
class OpenFFError(BaseException):
|
|
2
|
+
"""The base exception from which most custom exceptions in openff-utilities should inherit."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MissingOptionalDependencyError(OpenFFError, ImportError):
|
|
6
|
+
"""An exception raised when an optional dependency is required
|
|
7
|
+
but cannot be found.
|
|
8
|
+
|
|
9
|
+
Attributes
|
|
10
|
+
----------
|
|
11
|
+
library_name
|
|
12
|
+
The name of the missing library.
|
|
13
|
+
license_issue
|
|
14
|
+
Whether the library was importable but was unusable due
|
|
15
|
+
to a missing license.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, library_name: str, license_issue: bool = False):
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
library_name
|
|
24
|
+
The name of the missing library.
|
|
25
|
+
license_issue
|
|
26
|
+
Whether the library was importable but was unusable due
|
|
27
|
+
to a missing license.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
message = f"The required {library_name} module could not be imported."
|
|
31
|
+
|
|
32
|
+
if license_issue:
|
|
33
|
+
message = f"{message} This is due to a missing license."
|
|
34
|
+
|
|
35
|
+
library_name_corrected = library_name.replace(".", "-")
|
|
36
|
+
|
|
37
|
+
if "openeye" not in library_name:
|
|
38
|
+
message = (
|
|
39
|
+
f"{message} Try installing the package by running "
|
|
40
|
+
f"`conda install -c conda-forge {library_name_corrected}`"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
super().__init__(message)
|
|
44
|
+
|
|
45
|
+
self.library_name = library_name
|
|
46
|
+
self.license_issue = license_issue
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class CondaExecutableNotFoundError(OpenFFError):
|
|
50
|
+
"""
|
|
51
|
+
A conda (or mamba/micromamba) executable is not found.
|
|
52
|
+
"""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import warnings
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@functools.lru_cache
|
|
10
|
+
def _get_conda_list_package_versions() -> dict[str, str]:
|
|
11
|
+
"""
|
|
12
|
+
Returns the versions of any packages found while executing `conda list`.
|
|
13
|
+
If no conda executable is found, emits CondaExecutableNotFoundWarning
|
|
14
|
+
"""
|
|
15
|
+
from openff.utilities.warnings import CondaExecutableNotFoundWarning
|
|
16
|
+
|
|
17
|
+
if os.environ.get("PIXI_IN_SHELL") == "1" and os.environ.get("PIXI_EXE"):
|
|
18
|
+
conda_command = "{} list --json --manifest-path {}".format(
|
|
19
|
+
os.environ["PIXI_EXE"], os.environ["PIXI_PROJECT_MANIFEST"]
|
|
20
|
+
)
|
|
21
|
+
elif os.environ.get("CONDA_SHLVL", "0") != "0" and os.environ.get("CONDA_EXE"):
|
|
22
|
+
conda_command = "{} list --json".format(os.environ["CONDA_EXE"])
|
|
23
|
+
elif os.environ.get("CONDA_SHLVL", "0") != "0" and os.environ.get("MAMBA_EXE"):
|
|
24
|
+
conda_command = "{} list --json".format(os.environ["MAMBA_EXE"])
|
|
25
|
+
else:
|
|
26
|
+
warnings.warn(
|
|
27
|
+
"No conda/mamba/micromamba executable found. Unable to determine package versions.",
|
|
28
|
+
CondaExecutableNotFoundWarning,
|
|
29
|
+
)
|
|
30
|
+
return dict()
|
|
31
|
+
|
|
32
|
+
output = json.loads(subprocess.check_output(conda_command.split()).decode())
|
|
33
|
+
|
|
34
|
+
package_versions = {}
|
|
35
|
+
for _package in output:
|
|
36
|
+
package_versions[_package["name"]] = _package["version"]
|
|
37
|
+
|
|
38
|
+
return package_versions
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_ambertools_version() -> Optional[str]:
|
|
42
|
+
"""
|
|
43
|
+
Attempts to retrieve the version of the currently installed AmberTools.
|
|
44
|
+
|
|
45
|
+
There are two soft failure modes, each of which cause this function to return `None`:
|
|
46
|
+
1. If there is a failure calling `{conda|mamba|etc.} list`, the user is
|
|
47
|
+
warned by `_get_conda_list_package_versions` and this function returns `None`.
|
|
48
|
+
2. If there is a failure calling `{conda|mamba|etc.} list`, this function
|
|
49
|
+
still returns `None`, but without a warning associated with the above failure.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
return _get_conda_list_package_versions().get("ambertools", None)
|
|
54
|
+
except (
|
|
55
|
+
ValueError, # Issue 98
|
|
56
|
+
subprocess.CalledProcessError, # Issue 101
|
|
57
|
+
):
|
|
58
|
+
from openff.utilities.warnings import CondaExecutableNotFoundWarning
|
|
59
|
+
|
|
60
|
+
warnings.warn(
|
|
61
|
+
"Something went wrong parsing the output of `conda list` or similar. Unable to "
|
|
62
|
+
"determine AmberTools version, returning None.",
|
|
63
|
+
CondaExecutableNotFoundWarning,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
2
|
+
|
|
3
|
+
from openff.utilities.utilities import has_executable, has_package
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from _pytest.mark.structures import MarkDecorator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def skip_if_missing(package_name: str, reason: Optional[str] = None) -> "MarkDecorator":
|
|
10
|
+
"""
|
|
11
|
+
Helper function to generate a pytest.mark.skipif decorator
|
|
12
|
+
for any package. This allows tests to be skipped if some
|
|
13
|
+
optional dependency is not found.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
package_name : str
|
|
18
|
+
The name of the package that is required for a test(s)
|
|
19
|
+
reason : str, optional
|
|
20
|
+
Explanation of why the skipped it to be tested
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
requires_package : _pytest.mark.structures.MarkDecorator
|
|
25
|
+
A pytest decorator that will skip tests if the package is not available
|
|
26
|
+
"""
|
|
27
|
+
import pytest
|
|
28
|
+
|
|
29
|
+
if not reason:
|
|
30
|
+
reason = f"Package {package_name} is required, but was not found."
|
|
31
|
+
requires_package = pytest.mark.skipif(not has_package(package_name), reason=reason)
|
|
32
|
+
return requires_package
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def skip_if_missing_exec(exec: Union[str, list[str]]) -> "MarkDecorator":
|
|
36
|
+
"""Helper function to generate a pytest.mark.skipif decorator
|
|
37
|
+
if an executable(s) is not found."""
|
|
38
|
+
import pytest
|
|
39
|
+
|
|
40
|
+
execs: list[str]
|
|
41
|
+
if isinstance(exec, str):
|
|
42
|
+
execs = [exec]
|
|
43
|
+
elif isinstance(exec, list):
|
|
44
|
+
execs = exec
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError(f"Bad type passed to skip_if_missing_exec. Found type {type(exec)}")
|
|
47
|
+
|
|
48
|
+
found_exec = False
|
|
49
|
+
for exec_ in execs:
|
|
50
|
+
found_exec = found_exec or has_executable(exec_)
|
|
51
|
+
|
|
52
|
+
reason = f"Package {exec!s} is required, but was not found."
|
|
53
|
+
mark = pytest.mark.skipif(not found_exec, reason=reason)
|
|
54
|
+
return mark
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import errno
|
|
2
|
+
import importlib
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import Generator
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from importlib.resources import as_file, files
|
|
8
|
+
from tempfile import TemporaryDirectory
|
|
9
|
+
from typing import Any, Callable, Literal, Optional, TypeVar
|
|
10
|
+
|
|
11
|
+
from openff.utilities.exceptions import MissingOptionalDependencyError
|
|
12
|
+
|
|
13
|
+
# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
|
|
14
|
+
|
|
15
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def has_package(package_name: str) -> bool:
|
|
19
|
+
"""
|
|
20
|
+
Helper function to generically check if a Python package is installed.
|
|
21
|
+
Intended to be used to check for optional dependencies.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
package_name : str
|
|
26
|
+
The name of the Python package to check the availability of
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
package_available : bool
|
|
31
|
+
Boolean indicator if the package is available or not
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
>>> has_numpy = has_package('numpy')
|
|
36
|
+
>>> has_numpy
|
|
37
|
+
True
|
|
38
|
+
>>> has_foo = has_package('other_non_installed_package')
|
|
39
|
+
>>> has_foo
|
|
40
|
+
False
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
importlib.import_module(package_name)
|
|
44
|
+
except ModuleNotFoundError:
|
|
45
|
+
return False
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def requires_package(package_name: str) -> Callable[..., Any]:
|
|
50
|
+
"""
|
|
51
|
+
Helper function to denote that a funciton requires some optional
|
|
52
|
+
dependency. A function decorated with this decorator will raise
|
|
53
|
+
`MissingOptionalDependencyError` if the package is not found by
|
|
54
|
+
`importlib.import_module()`.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
package_name : str
|
|
59
|
+
The name of the module to be imported.
|
|
60
|
+
|
|
61
|
+
Raises
|
|
62
|
+
------
|
|
63
|
+
MissingOptionalDependencyError
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def inner_decorator(function: F) -> F:
|
|
68
|
+
@wraps(function)
|
|
69
|
+
def wrapper(*args, **kwargs): # type: ignore[no-untyped-def]
|
|
70
|
+
import importlib
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
importlib.import_module(package_name)
|
|
74
|
+
except ImportError:
|
|
75
|
+
raise MissingOptionalDependencyError(library_name=package_name)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
raise e
|
|
78
|
+
|
|
79
|
+
return function(*args, **kwargs)
|
|
80
|
+
|
|
81
|
+
return wrapper # type: ignore[return-value]
|
|
82
|
+
|
|
83
|
+
return inner_decorator
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def requires_oe_module(
|
|
87
|
+
module_name: Literal["oechem", "oeomega", "oequacpac", "oeiupac", "oedepict"],
|
|
88
|
+
) -> Callable[..., Any]:
|
|
89
|
+
"""
|
|
90
|
+
Helper function to denote that a funciton requires a particular OpenEye library.
|
|
91
|
+
A function decorated with this decorator will raise `MissingOptionalDependencyError` if
|
|
92
|
+
the module is not found by @requires_package or the module is not found to be
|
|
93
|
+
licensed.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
module_name : str
|
|
98
|
+
The name of the OpenEye module to be imported.
|
|
99
|
+
|
|
100
|
+
Raises
|
|
101
|
+
------
|
|
102
|
+
MissingOptionalDependencyError
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def inner_decorator(function: F) -> F:
|
|
106
|
+
@requires_package(f"openeye.{module_name}")
|
|
107
|
+
@wraps(function)
|
|
108
|
+
def wrapper(*args, **kwargs): # type: ignore[no-untyped-def]
|
|
109
|
+
oe_module = importlib.import_module(f"openeye.{module_name}")
|
|
110
|
+
|
|
111
|
+
license_functions = {
|
|
112
|
+
"oechem": "OEChemIsLicensed",
|
|
113
|
+
"oequacpac": "OEQuacPacIsLicensed",
|
|
114
|
+
"oeiupac": "OEIUPACIsLicensed",
|
|
115
|
+
"oeomega": "OEOmegaIsLicensed",
|
|
116
|
+
"oedepict": "OEDepictIsLicensed",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
is_licensed = getattr(oe_module, license_functions[module_name])()
|
|
120
|
+
|
|
121
|
+
if not is_licensed:
|
|
122
|
+
raise MissingOptionalDependencyError(library_name=f"openeye.{module_name}", license_issue=True)
|
|
123
|
+
|
|
124
|
+
return function(*args, **kwargs)
|
|
125
|
+
|
|
126
|
+
return wrapper # type: ignore
|
|
127
|
+
|
|
128
|
+
return inner_decorator
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def has_executable(program_name: str) -> bool:
|
|
132
|
+
import os
|
|
133
|
+
|
|
134
|
+
def _is_executable(fpath: str) -> bool:
|
|
135
|
+
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
|
136
|
+
|
|
137
|
+
fpath, fname = os.path.split(program_name)
|
|
138
|
+
if fpath:
|
|
139
|
+
if _is_executable(program_name):
|
|
140
|
+
return True
|
|
141
|
+
else:
|
|
142
|
+
for path in os.environ["PATH"].split(os.pathsep):
|
|
143
|
+
path = path.strip('"')
|
|
144
|
+
exe_file = os.path.join(path, program_name)
|
|
145
|
+
if _is_executable(exe_file):
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@contextmanager
|
|
152
|
+
def temporary_cd(directory_path: Optional[str] = None) -> Generator[None, None, None]:
|
|
153
|
+
"""Temporarily move the current working directory to the path
|
|
154
|
+
specified. If no path is given, a temporary directory will be
|
|
155
|
+
created, moved into, and then destroyed when the context manager
|
|
156
|
+
is closed.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
directory_path: str, optional
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
if directory_path is not None and len(directory_path) == 0:
|
|
168
|
+
yield
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
old_directory = os.getcwd()
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
if directory_path is None:
|
|
175
|
+
with TemporaryDirectory() as new_directory:
|
|
176
|
+
os.chdir(new_directory)
|
|
177
|
+
yield
|
|
178
|
+
|
|
179
|
+
else:
|
|
180
|
+
os.chdir(directory_path)
|
|
181
|
+
yield
|
|
182
|
+
|
|
183
|
+
finally:
|
|
184
|
+
os.chdir(old_directory)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def get_data_dir_path(relative_path: str, package_name: str) -> str:
|
|
188
|
+
"""Get the full path to a directory within a module's tree.
|
|
189
|
+
|
|
190
|
+
If no directory is found at `relative_path`, a second attempt will be made
|
|
191
|
+
with `data/` preprended. If no directory is found at path, a NotADirectoryError
|
|
192
|
+
is raised.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
relative_path : str
|
|
197
|
+
The relative path of the file to load.
|
|
198
|
+
package_name : str
|
|
199
|
+
The name of the package in which a file is to be loaded, i.e. "openff.toolkit" or "openff.evaluator".
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
The absolute path to the file.
|
|
204
|
+
|
|
205
|
+
Raises
|
|
206
|
+
------
|
|
207
|
+
NotADirectoryError
|
|
208
|
+
|
|
209
|
+
See Also
|
|
210
|
+
--------
|
|
211
|
+
get_data_file_path, for getting the path to a particular file in a data directory.
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
with as_file(files(package_name) / relative_path) as dir_path:
|
|
215
|
+
if dir_path.is_dir():
|
|
216
|
+
return dir_path.as_posix()
|
|
217
|
+
|
|
218
|
+
with as_file(files(package_name) / "data" / relative_path) as dir_path:
|
|
219
|
+
if dir_path.is_dir():
|
|
220
|
+
return dir_path.as_posix()
|
|
221
|
+
|
|
222
|
+
raise NotADirectoryError(f"Directory {relative_path} not found in {package_name}.")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def get_data_file_path(relative_path: str, package_name: str) -> str:
|
|
226
|
+
"""Get the full path to one of the files in the data directory.
|
|
227
|
+
|
|
228
|
+
If no file is found at `relative_path`, a second attempt will be made
|
|
229
|
+
with `data/` preprended. If no files exist at either path, a FileNotFoundError
|
|
230
|
+
is raised.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
relative_path : str
|
|
235
|
+
The relative path of the file to load.
|
|
236
|
+
package_name : str
|
|
237
|
+
The name of the package in which a file is to be loaded, i.e.
|
|
238
|
+
"openff.toolkit" or "openff.evaluator"
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
The absolute path to the file.
|
|
243
|
+
|
|
244
|
+
Raises
|
|
245
|
+
------
|
|
246
|
+
FileNotFoundError
|
|
247
|
+
|
|
248
|
+
See Also
|
|
249
|
+
--------
|
|
250
|
+
get_data_dir_path, for getting the path to a directory instead of an individual file.
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
with as_file(files(package_name) / relative_path) as file_path:
|
|
254
|
+
if file_path.is_file():
|
|
255
|
+
return file_path.as_posix()
|
|
256
|
+
|
|
257
|
+
with as_file(files(package_name) / "data" / relative_path) as file_path:
|
|
258
|
+
if file_path.is_file():
|
|
259
|
+
return file_path.as_posix()
|
|
260
|
+
|
|
261
|
+
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), file_path)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"setuptools",
|
|
4
|
+
"versioningit",
|
|
5
|
+
]
|
|
6
|
+
build-backend = "setuptools.build_meta"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "acellera-openff-utilities"
|
|
10
|
+
description = "A collection of miscellaneous utility functions used throughout the OpenFF stack"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
authors = [{name = "Open Force Field Initiative", email = "info@openforcefield.org"}]
|
|
13
|
+
license = {text = "MIT"}
|
|
14
|
+
dynamic = ["version"]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.packages]
|
|
17
|
+
find = {}
|
|
18
|
+
|
|
19
|
+
[tool.versioningit]
|
|
20
|
+
|
|
21
|
+
[tool.ruff]
|
|
22
|
+
line-length = 119
|
|
23
|
+
namespace-packages = ["openff/utilities/"]
|
|
24
|
+
|
|
25
|
+
[tool.ruff.lint]
|
|
26
|
+
select = ["F", "E", "W", "NPY", "UP", "RUF", "I"]
|
|
27
|
+
|
|
28
|
+
[tool.ruff.lint.per-file-ignores]
|
|
29
|
+
"openff/utilities/__init__.py" = ["F401"]
|
|
30
|
+
|
|
31
|
+
[tool.mypy]
|
|
32
|
+
exclude="openff/utilities/_tests"
|
|
33
|
+
python_version = "3.12"
|
|
34
|
+
warn_unused_configs = true
|
|
35
|
+
warn_unused_ignores = true
|
|
36
|
+
show_error_codes = true
|
|
37
|
+
strict = true
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
markers = [
|
|
41
|
+
"leaky: run leaky tests",
|
|
42
|
+
]
|
|
43
|
+
addopts = "--cov=openff/utilities --cov-report=xml -m \"not leaky\""
|
|
44
|
+
|
|
45
|
+
[tool.coverage.run]
|
|
46
|
+
omit = [
|
|
47
|
+
"*/*/_tests/*",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.coverage.report]
|
|
51
|
+
exclude_lines = [
|
|
52
|
+
"pragma: no cover",
|
|
53
|
+
"if TYPE_CHECKING:",
|
|
54
|
+
"raise NotImplementedError",
|
|
55
|
+
"@overload",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.pixi.project]
|
|
59
|
+
channels = ["conda-forge", "openeye"]
|
|
60
|
+
platforms = ["linux-64", "osx-64", "osx-arm64"]
|
|
61
|
+
|
|
62
|
+
[tool.pixi.dependencies]
|
|
63
|
+
python = "*"
|
|
64
|
+
pip = "*"
|
|
65
|
+
versioningit = "*"
|
|
66
|
+
pytest = "*"
|
|
67
|
+
pytest-cov = "*"
|
|
68
|
+
pytest-xdist = "*"
|
|
69
|
+
pytest-randomly = "*"
|
|
70
|
+
nbval = "*"
|
|
71
|
+
mypy = "*"
|
|
72
|
+
types-setuptools = "*"
|
|
73
|
+
types-toml = "*"
|
|
74
|
+
|
|
75
|
+
[tool.pixi.pypi-dependencies]
|
|
76
|
+
openff-utilities = { path = ".", editable = true }
|
|
77
|
+
|
|
78
|
+
[tool.pixi.feature.py311.dependencies]
|
|
79
|
+
python = "3.11.*"
|
|
80
|
+
[tool.pixi.feature.py312.dependencies]
|
|
81
|
+
python = "3.12.*"
|
|
82
|
+
|
|
83
|
+
[tool.pixi.environments]
|
|
84
|
+
py311 = ["py311"]
|
|
85
|
+
py312 = ["py312"]
|