Skip to content

Commit 9c34f94

Browse files
authored
Drop support for python 3.7; migrate toolz & pint accordingly (#196)
Drop support for python 3.7; migrate toolz & pint accordingly
1 parent ebcd2bb commit 9c34f94

File tree

7 files changed

+66
-95
lines changed

7 files changed

+66
-95
lines changed

gemd/entity/value/discrete_categorical.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
"""Discrete distribution across several categories."""
2-
from toolz import keymap
3-
42
from gemd.entity.setters import validate_str
53
from gemd.entity.value.categorical_value import CategoricalValue
64
from gemd.entity.bounds import CategoricalBounds
@@ -41,7 +39,7 @@ def probabilities(self, probabilities: dict):
4139
elif isinstance(probabilities, dict):
4240
if abs(sum(probabilities.values()) - 1.0) > 1.0e-9:
4341
raise ValueError("probabilities must sum to 1.0")
44-
self._probabilities = keymap(validate_str, probabilities)
42+
self._probabilities = {validate_str(k): v for k, v in probabilities.items()}
4543
else:
4644
raise TypeError("probabilities must be dict or single value")
4745

gemd/units/impl.py

Lines changed: 49 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Implementation of units."""
22
import functools
33
from importlib.resources import read_text
4+
import os
45
from pathlib import Path
56
import re
67
from tempfile import TemporaryDirectory
@@ -15,18 +16,15 @@
1516
from pint.errors import UndefinedUnitError, DefinitionSyntaxError # noqa Import
1617

1718
# Store directories so they don't get auto-cleaned until exit
18-
_TEMP_DIRECTORIES = []
19+
_TEMP_DIRECTORY = TemporaryDirectory()
1920

2021

2122
def _deploy_default_files() -> str:
2223
"""Copy the units & constants file into a temporary directory."""
23-
default_dir = TemporaryDirectory()
24-
_TEMP_DIRECTORIES.append(default_dir)
25-
26-
units_path = Path(default_dir.name) / "citrine_en.txt"
24+
units_path = Path(_TEMP_DIRECTORY.name) / "citrine_en.txt"
2725
units_path.write_text(read_text("gemd.units", "citrine_en.txt"))
2826

29-
constants_path = Path(default_dir.name) / "constants_en.txt"
27+
constants_path = Path(_TEMP_DIRECTORY.name) / "constants_en.txt"
3028
constants_path.write_text(read_text("gemd.units", "constants_en.txt"))
3129

3230
return str(units_path)
@@ -166,8 +164,8 @@ def _scaling_store_and_mangle(input_string: str, todo: List[Tuple[str, str, str]
166164

167165
if unit_string is not None:
168166
stripped_unit = re.sub(r"[+\s]+", "", unit_string).replace("--", "")
169-
long_unit = f"{_REGISTRY(stripped_unit).u}"
170-
short_unit = f"{_REGISTRY(stripped_unit).u:~}"
167+
long_unit = f"{_REGISTRY.parse_units(stripped_unit)}"
168+
short_unit = f"{_REGISTRY.parse_units(stripped_unit):~}"
171169
long = stripped.replace(stripped_unit, "_" + long_unit)
172170
short = stripped.replace(stripped_unit, " " + short_unit)
173171
else:
@@ -221,41 +219,10 @@ def convert_units(value: float, starting_unit: str, final_unit: str) -> float:
221219
if starting_unit == final_unit:
222220
return value # skip computation
223221
else:
224-
resolved_final_unit = _REGISTRY(final_unit).u # `to` bypasses preparser
222+
resolved_final_unit = _REGISTRY.parse_units(final_unit) # `to` bypasses preparser
225223
return _REGISTRY.Quantity(value, starting_unit).to(resolved_final_unit).magnitude
226224

227225

228-
def change_definitions_file(filename: str = None):
229-
"""
230-
Change which file is used for units definition.
231-
232-
Parameters
233-
----------
234-
filename: str
235-
The file to use
236-
237-
"""
238-
global _REGISTRY
239-
convert_units.cache_clear() # Units will change
240-
if filename is None:
241-
target = DEFAULT_FILE
242-
else:
243-
# TODO: Handle case where user provides a units file but no constants file
244-
target = Path(filename).expanduser().resolve(strict=True)
245-
246-
# TODO: Pint 0.18 doesn't accept paths; must stringify
247-
_REGISTRY = UnitRegistry(filename=str(target),
248-
preprocessors=[_space_after_minus_preprocessor,
249-
_scientific_notation_preprocessor,
250-
_scaling_preprocessor
251-
],
252-
autoconvert_offset_to_baseunit=True
253-
)
254-
255-
256-
change_definitions_file() # initialize to default
257-
258-
259226
@register_unit_format("clean")
260227
def _format_clean(unit, registry, **options):
261228
"""Formatter that turns scaling-factor-units into numbers again."""
@@ -285,36 +252,6 @@ def _format_clean(unit, registry, **options):
285252

286253

287254
@functools.lru_cache(maxsize=1024)
288-
def _parse_units(units: str) -> Unit:
289-
"""
290-
Parse a string or Unit into a standard string representation of the unit.
291-
292-
Parameters
293-
----------
294-
units: Union[str, Unit, None]
295-
The string or Unit representation of the object we wish to display
296-
297-
Returns
298-
-------
299-
[Union[str, Unit, None]]
300-
The representation; note that the same type that was passed is returned
301-
302-
"""
303-
# TODO: parse_units has a bug resolved in 0.19, but 3.7 only supports up to 0.18
304-
parsed = _REGISTRY(units)
305-
try:
306-
magnitude = parsed.magnitude
307-
result = parsed.units
308-
except AttributeError: # It was non-dimensional
309-
magnitude = parsed
310-
result = _REGISTRY("").u
311-
if magnitude == 0.0:
312-
raise ValueError(f"Unit expression had a zero scaling factor. {units}")
313-
if magnitude != 1:
314-
raise ValueError(f"Unit expression cannot have a leading scaling factor. {units}")
315-
return result
316-
317-
318255
def parse_units(units: Union[str, Unit, None],
319256
*,
320257
return_unit: bool = False
@@ -337,11 +274,11 @@ def parse_units(units: Union[str, Unit, None],
337274
"""
338275
if units is None:
339276
if return_unit:
340-
return _REGISTRY("").u
277+
return _REGISTRY.parse_units("")
341278
else:
342279
return None
343280
elif isinstance(units, str):
344-
parsed = _parse_units(units)
281+
parsed = _REGISTRY.parse_units(units)
345282
if return_unit:
346283
return parsed
347284
else:
@@ -369,7 +306,46 @@ def get_base_units(units: Union[str, Unit]) -> Tuple[Unit, float, float]:
369306
370307
"""
371308
if isinstance(units, str):
372-
units = _REGISTRY(units).u
309+
units = _REGISTRY.parse_units(units)
373310
ratio, base_unit = _REGISTRY.get_base_units(units)
374311
offset = _REGISTRY.Quantity(0, units).to(_REGISTRY.Quantity(0, base_unit)).magnitude
375312
return base_unit, float(ratio), offset
313+
314+
315+
def change_definitions_file(filename: str = None):
316+
"""
317+
Change which file is used for units definition.
318+
319+
Parameters
320+
----------
321+
filename: str
322+
The file to use
323+
324+
"""
325+
global _REGISTRY
326+
convert_units.cache_clear() # Units will change
327+
parse_units.cache_clear()
328+
get_base_units.cache_clear()
329+
if filename is None:
330+
target = DEFAULT_FILE
331+
else:
332+
# TODO: Handle case where user provides a units file but no constants file
333+
target = Path(filename).expanduser().resolve(strict=True)
334+
335+
current_dir = Path.cwd()
336+
try:
337+
path = Path(target)
338+
os.chdir(path.parent)
339+
# Need to re-verify path because of some slippiness around tmp on MacOS
340+
_REGISTRY = UnitRegistry(filename=Path.cwd() / path.name,
341+
preprocessors=[_space_after_minus_preprocessor,
342+
_scientific_notation_preprocessor,
343+
_scaling_preprocessor
344+
],
345+
autoconvert_offset_to_baseunit=True
346+
)
347+
finally:
348+
os.chdir(current_dir)
349+
350+
351+
change_definitions_file() # initialize to default

gemd/util/impl.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
from gemd.entity.dict_serializable import DictSerializable
1010
from gemd.entity.link_by_uid import LinkByUID
1111

12-
from toolz import concatv
13-
1412

1513
def set_uuids(obj, scope):
1614
"""
@@ -441,8 +439,8 @@ def recursive_foreach(obj: Union[Iterable, BaseEntity, DictSerializable],
441439
func(this)
442440

443441
if cached_isinstance(this, Mapping):
444-
for x in concatv(this.keys(), this.values()):
445-
queue.append(x)
442+
queue.extend(this.keys())
443+
queue.extend(this.values())
446444
elif cached_isinstance(this, DictSerializable):
447445
for k, x in this.__dict__.items():
448446
queue.append(x)
@@ -496,7 +494,8 @@ def recursive_flatmap(obj: Union[Iterable, BaseEntity, DictSerializable],
496494
res.extend(func(this))
497495

498496
if cached_isinstance(this, Mapping):
499-
queue.extend(concatv(this.keys(), this.values()))
497+
queue.extend(this.keys())
498+
queue.extend(this.values())
500499
elif cached_isinstance(this, DictSerializable):
501500
for k, x in sorted(this.__dict__.items()):
502501
if unidirectional and cached_isinstance(this, BaseEntity) and k in this.skip:

requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
toolz==0.11.0
2-
pint==0.18
1+
pint==0.20
32
sphinx==4.3.0
43
sphinxcontrib-apidoc==0.3.0
54
sphinx-rtd-theme==1.0.0

setup.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
packages.append("")
55

66
setup(name='gemd',
7-
version='1.16.3',
8-
python_requires='>=3.7',
7+
version='1.16.4',
8+
python_requires='>=3.8',
99
url='http://github.com/CitrineInformatics/gemd-python',
1010
description="Python binding for Citrine's GEMD data model",
1111
author='Citrine Informatics',
@@ -23,8 +23,7 @@
2323
'tests.units': ['test_units.txt']
2424
},
2525
install_requires=[
26-
"toolz>=0.11.0,<1",
27-
"pint>=0.18,<1",
26+
"pint>=0.20,<1",
2827
"deprecation>=2.1.0,<3"
2928
],
3029
extras_require={
@@ -40,7 +39,6 @@
4039
},
4140
classifiers=[
4241
'Programming Language :: Python :: 3',
43-
'Programming Language :: Python :: 3.7',
4442
'Programming Language :: Python :: 3.8',
4543
'Programming Language :: Python :: 3.9',
4644
'Programming Language :: Python :: 3.10',

test_requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ flake8==3.7.8
22
flake8-docstrings==1.3.1
33
pytest==7.3.1
44
pytest-cov==4.0.0
5-
pandas>=1.3.5,<=1.5.0
5+
pandas==1.5.0
6+
toolz==0.12.0
67
derp==0.1.1

tests/units/test_parser.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,18 @@ def test_parse_units_as_units():
112112
def test_format():
113113
"""Test that custom formatting behaves as we hope."""
114114
# use the default unit registry for now
115-
reg = UnitRegistry(filename=DEFAULT_FILE)
115+
reg = UnitRegistry()
116116

117117
result = parse_units("K^-2.0 m^-1e0 C^0 g^1 s^2")
118118
assert "-" not in result
119-
assert "[time]" in reg(result).dimensionality
120-
assert "[current]" not in reg(result).dimensionality
119+
assert "[time]" in str(reg(result).dimensionality)
120+
assert "[current]" not in str(reg(result).dimensionality)
121121
kelvin = str(reg("K").units)
122122
gram = str(reg("g").units)
123123
second = str(reg("s").units)
124-
assert kelvin in result
125-
assert gram in result
126-
assert second in result
124+
assert kelvin in str(result)
125+
assert gram in str(result)
126+
assert second in str(result)
127127
assert result.index(gram) < result.index(kelvin)
128128
assert result.index(gram) < result.index(second)
129129

0 commit comments

Comments
 (0)