-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test with Pint 0.23, add downstream testing (#83)
* Update linters * Test with Pint 0.23 * Add downstream package for testing * Support Python 3.9 * Upload to CodeCov with token
- Loading branch information
1 parent
cfdb193
commit b8fdc8b
Showing
8 changed files
with
397 additions
and
152 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
""" | ||
See openff/toolkit/utils/utils.py | ||
""" | ||
|
||
import functools | ||
from typing import Iterable, Union | ||
|
||
from openff.units import Quantity, unit | ||
|
||
|
||
def string_to_quantity(quantity_string) -> Union[str, int, float, Quantity]: | ||
"""Attempt to parse a string into a unit.Quantity. | ||
Note that dimensionless floats and ints are returns as floats or ints, not Quantity objects. | ||
""" | ||
|
||
from tokenize import TokenError | ||
|
||
from pint import UndefinedUnitError | ||
|
||
try: | ||
quantity = Quantity(quantity_string) | ||
except (TokenError, UndefinedUnitError): | ||
return quantity_string | ||
|
||
# TODO: Should intentionally unitless array-likes be Quantity objects | ||
# or their raw representation? | ||
if (quantity.units == unit.dimensionless) and isinstance(quantity.m, (int, float)): | ||
return quantity.m | ||
else: | ||
return quantity | ||
|
||
|
||
def convert_all_strings_to_quantity( | ||
smirnoff_data: dict, | ||
ignore_keys: Iterable[str] = tuple(), | ||
): | ||
""" | ||
Traverses a SMIRNOFF data structure, attempting to convert all | ||
quantity-defining strings into openff.units.unit.Quantity objects. | ||
Integers and floats are ignored and not converted into a dimensionless | ||
``openff.units.unit.Quantity`` object. | ||
Parameters | ||
---------- | ||
smirnoff_data | ||
A hierarchical dict structured in compliance with the SMIRNOFF spec | ||
ignore_keys | ||
A list of keys to skip when converting strings to quantities | ||
Returns | ||
------- | ||
converted_smirnoff_data | ||
A hierarchical dict structured in compliance with the SMIRNOFF spec, | ||
with quantity-defining strings converted to openff.units.unit.Quantity objects | ||
""" | ||
from pint import DefinitionSyntaxError | ||
|
||
if isinstance(smirnoff_data, dict): | ||
for key, value in smirnoff_data.items(): | ||
if key in ignore_keys: | ||
smirnoff_data[key] = value | ||
else: | ||
smirnoff_data[key] = convert_all_strings_to_quantity( | ||
value, | ||
ignore_keys=ignore_keys, | ||
) | ||
obj_to_return = smirnoff_data | ||
|
||
elif isinstance(smirnoff_data, list): | ||
for index, item in enumerate(smirnoff_data): | ||
smirnoff_data[index] = convert_all_strings_to_quantity( | ||
item, | ||
ignore_keys=ignore_keys, | ||
) | ||
obj_to_return = smirnoff_data | ||
|
||
elif isinstance(smirnoff_data, int) or isinstance(smirnoff_data, float): | ||
obj_to_return = smirnoff_data | ||
|
||
else: | ||
try: | ||
obj_to_return = object_to_quantity(smirnoff_data) | ||
except (TypeError, DefinitionSyntaxError): | ||
obj_to_return = smirnoff_data | ||
|
||
return obj_to_return | ||
|
||
|
||
@functools.singledispatch | ||
def object_to_quantity(object): | ||
""" | ||
Attempts to turn the provided object into openmm.unit.Quantity(s). | ||
Can handle float, int, strings, quantities, or iterators over | ||
the same. Raises an exception if unable to convert all inputs. | ||
Parameters | ||
---------- | ||
object | ||
The object to convert to a ``openmm.unit.Quantity`` object. | ||
Returns | ||
------- | ||
converted_object | ||
""" | ||
# If we can't find a custom type, we treat this as a generic iterator. | ||
return [object_to_quantity(sub_obj) for sub_obj in object] | ||
|
||
|
||
@object_to_quantity.register(Quantity) | ||
def _(obj): | ||
return obj | ||
|
||
|
||
@object_to_quantity.register(str) | ||
def _(obj): | ||
import pint | ||
|
||
try: | ||
return string_to_quantity(obj) | ||
except pint.errors.UndefinedUnitError: | ||
raise ValueError | ||
|
||
|
||
@object_to_quantity.register(int) | ||
@object_to_quantity.register(float) | ||
def _(obj): | ||
return Quantity(obj) | ||
|
||
|
||
try: | ||
import openmm | ||
|
||
from openff.units.openmm import from_openmm | ||
|
||
@object_to_quantity.register(openmm.unit.Quantity) | ||
def _(obj): | ||
return from_openmm(obj) | ||
|
||
except ImportError: | ||
pass # pragma: nocover |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
""" | ||
A test package used to ensure that downstream packages can do basic things with openff-units. | ||
""" | ||
|
||
from setuptools import setup | ||
|
||
setup( | ||
version="0.0.0", | ||
name="openff-units-downstream-dummy", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from typing import Union | ||
|
||
import pytest | ||
from dummy import object_to_quantity | ||
|
||
from openff.units import Quantity, Unit | ||
|
||
|
||
def test_function_can_be_defined(): | ||
""" | ||
Just make sure a function can be defined using these classes. | ||
Safeguard against i.e. https://github.com/openforcefield/openff-toolkit/issues/1632 | ||
Type checkers might not be happy. | ||
""" | ||
|
||
def dummy_function( | ||
unit: Unit, | ||
quantity: Quantity, | ||
extra: Union[Unit, Quantity, str, None] = None, | ||
): | ||
return f"{unit} {quantity} {extra}" | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"input, output", | ||
[ | ||
("1.0 * kilocalories_per_mole", Quantity(1.0, "kilocalories_per_mole")), | ||
(Quantity("2.0 * angstrom"), Quantity(2.0, "angstrom")), | ||
(3.0 * Unit("nanometer"), Quantity(3.0, "nanometer")), | ||
(4, Quantity(4.0)), | ||
(5.0, Quantity(5.0)), | ||
], | ||
) | ||
def test_object_to_quantity(input, output): | ||
assert object_to_quantity(input) == output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,33 +2,32 @@ | |
openff-toolkit | ||
A common units module for the OpenFF software stack | ||
""" | ||
|
||
import sys | ||
from setuptools import setup, find_namespace_packages | ||
|
||
from setuptools import find_namespace_packages, setup | ||
|
||
import versioneer | ||
|
||
short_description = __doc__.split("\n") | ||
|
||
needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) | ||
pytest_runner = ['pytest-runner'] if needs_pytest else [] | ||
needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) | ||
pytest_runner = ["pytest-runner"] if needs_pytest else [] | ||
|
||
try: | ||
with open("README.md", "r") as handle: | ||
long_description = handle.read() | ||
except: | ||
long_description = "\n".join(short_description[2:]) | ||
long_description = open("README.md", "r").read() | ||
|
||
|
||
setup( | ||
name='openff-units', | ||
author='Open Force Field Initiative', | ||
author_email='[email protected]', | ||
name="openff-units", | ||
author="Open Force Field Initiative", | ||
author_email="[email protected]", | ||
description=short_description[0], | ||
long_description=long_description, | ||
long_description_content_type="text/markdown", | ||
version=versioneer.get_version(), | ||
cmdclass=versioneer.get_cmdclass(), | ||
license='MIT', | ||
packages=find_namespace_packages(include=['openff.*']), | ||
license="MIT", | ||
packages=find_namespace_packages(include=["openff.*"]), | ||
package_data={"openff.units": ["py.typed"]}, | ||
include_package_data=True, | ||
setup_requires=[] + pytest_runner, | ||
|
Oops, something went wrong.