Skip to content

Commit

Permalink
Merge pull request #1499 from hgrecco/develop
Browse files Browse the repository at this point in the history
Provide a method to iter the definitions in the order they appear
  • Loading branch information
hgrecco authored Apr 6, 2022
2 parents 8a6842d + c338b35 commit 56ac143
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 13 deletions.
3 changes: 2 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Pint Changelog
0.20 (unreleased)
-----------------

- Nothing changed yet.
- Provide a method to iter the definitions in the order they appear,
recursing the imported files. (Issue #1498)


0.19 (2022-04-04)
Expand Down
39 changes: 38 additions & 1 deletion pint/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,44 @@ def has_errors(self):
class DefinitionFiles(tuple):
"""Wrapper class that allows handling a tuple containing DefinitionFile."""

pass
@staticmethod
def _iter_definitions(
pending_files: list[DefinitionFile],
) -> Generator[Tuple[int, Definition]]:
"""Internal method to iterate definitions.
pending_files is a mutable list of definitions files
and elements are being removed as they are yielded.
"""
if not pending_files:
return
current_file = pending_files.pop(0)
for lineno, definition in current_file.parsed_lines:
if isinstance(definition, ImportDefinition):
if not pending_files:
raise ValueError(
f"No more files while trying to import {definition.path}."
)

if not str(pending_files[0].filename).endswith(definition.path):
raise ValueError(
"The order of the files do not match. "
f"(expected: {definition.path}, "
f"found {pending_files[0].filename})"
)

yield from DefinitionFiles._iter_definitions(pending_files)
else:
yield lineno, definition

def iter_definitions(self):
"""Iter all definitions in the order they appear,
going into the included files.
Important: This assumes that the order of the imported files
is the one that they will appear in the definitions.
"""
yield from self._iter_definitions(list(self))


def build_disk_cache_class(non_int_type: type):
Expand Down
21 changes: 10 additions & 11 deletions pint/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,17 +637,16 @@ def load_definitions(self, file, is_resource: bool = False):
else:
parsed_files = parser.DefinitionFiles([p.parse_lines(file)])

for definition_file in parsed_files[::-1]:
for lineno, definition in definition_file.parsed_lines:
if definition.__class__ in p.handled_classes:
continue
loaderfunc = loaders.get(definition.__class__, None)
if not loaderfunc:
raise ValueError(
f"No loader function defined "
f"for {definition.__class__.__name__}"
)
loaderfunc(definition)
for lineno, definition in parsed_files.iter_definitions():
if definition.__class__ in p.handled_classes:
continue
loaderfunc = loaders.get(definition.__class__, None)
if not loaderfunc:
raise ValueError(
f"No loader function defined "
f"for {definition.__class__.__name__}"
)
loaderfunc(definition)

return parsed_files

Expand Down
100 changes: 100 additions & 0 deletions pint/testsuite/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,3 +901,103 @@ def test_issue925(module_registry, callable, q_params):
type_before = type(q._magnitude)
callable(q)
assert isinstance(q._magnitude, type_before)

def test_issue1498(tmp_path):
def0 = tmp_path / "def0.txt"
def1 = tmp_path / "def1.txt"
def2 = tmp_path / "def2.txt"

# A file that defines a new base unit and uses it in a context
def0.write_text(
"""
foo = [FOO]
@context BAR
[FOO] -> [mass]: value / foo * 10.0 kg
@end
"""
)

# A file that defines a new base unit, then imports another file…
def1.write_text(
f"""
foo = [FOO]
@import {str(def2)}
"""
)

# …that, in turn, uses it in a context
def2.write_text(
"""
@context BAR
[FOO] -> [mass]: value / foo * 10.0 kg
@end
"""
)

# Succeeds with pint 0.18; fails with pint 0.19
ureg1 = UnitRegistry()
ureg1.load_definitions(def1) # ← FAILS

assert 12.0 == ureg1("1.2 foo").to("kg", "BAR").magnitude

def test_issue1498b(tmp_path):
def0 = tmp_path / "def0.txt"
def1 = tmp_path / "def1.txt"
def1_1 = tmp_path / "def1_1.txt"
def1_2 = tmp_path / "def1_2.txt"
def2 = tmp_path / "def2.txt"

# A file that defines a new base unit and uses it in a context
def0.write_text(
"""
foo = [FOO]
@context BAR
[FOO] -> [mass]: value / foo * 10.0 kg
@end
@import def1.txt
@import def2.txt
"""
)

# A file that defines a new base unit, then imports another file…
def1.write_text(
"""
@import def1_1.txt
@import def1_2.txt
"""
)

def1_1.write_text(
"""
@context BAR1_1
[FOO] -> [mass]: value / foo * 10.0 kg
@end
"""
)

def1_2.write_text(
"""
@context BAR1_2
[FOO] -> [mass]: value / foo * 10.0 kg
@end
"""
)

# …that, in turn, uses it in a context
def2.write_text(
"""
@context BAR2
[FOO] -> [mass]: value / foo * 10.0 kg
@end
"""
)

# Succeeds with pint 0.18; fails with pint 0.19
ureg1 = UnitRegistry()
ureg1.load_definitions(def0) # ← FAILS

assert 12.0 == ureg1("1.2 foo").to("kg", "BAR").magnitude

0 comments on commit 56ac143

Please sign in to comment.