1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
"""
pint.delegates.base_defparser
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Common class and function for all parsers.
:copyright: 2022 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import annotations
import functools
import itertools
import numbers
import pathlib
import typing as ty
from dataclasses import dataclass, field
from pint import errors
from pint.facets.plain.definitions import NotNumeric
from pint.util import ParserHelper, UnitsContainer
from .._vendor import flexcache as fc
from .._vendor import flexparser as fp
@dataclass(frozen=True)
class ParserConfig:
"""Configuration used by the parser."""
#: Indicates the output type of non integer numbers.
non_int_type: ty.Type[numbers.Number] = float
def to_scaled_units_container(self, s: str):
return ParserHelper.from_string(s, self.non_int_type)
def to_units_container(self, s: str):
v = self.to_scaled_units_container(s)
if v.scale != 1:
raise errors.UnexpectedScaleInContainer(str(v.scale))
return UnitsContainer(v)
def to_dimension_container(self, s: str):
v = self.to_units_container(s)
invalid = tuple(itertools.filterfalse(errors.is_valid_dimension_name, v.keys()))
if invalid:
raise errors.DefinitionSyntaxError(
f"Cannot build a dimension container with {', '.join(invalid)} that "
+ errors.MSG_INVALID_DIMENSION_NAME
)
return v
def to_number(self, s: str) -> numbers.Number:
"""Try parse a string into a number (without using eval).
The string can contain a number or a simple equation (3 + 4)
Raises
------
_NotNumeric
If the string cannot be parsed as a number.
"""
val = self.to_scaled_units_container(s)
if len(val):
raise NotNumeric(s)
return val.scale
@functools.lru_cache
def build_disk_cache_class(non_int_type: type):
"""Build disk cache class, taking into account the non_int_type."""
@dataclass(frozen=True)
class PintHeader(fc.InvalidateByExist, fc.NameByFields, fc.BasicPythonHeader):
from .. import __version__
pint_version: str = __version__
non_int_type: str = field(default_factory=lambda: non_int_type.__qualname__)
class PathHeader(fc.NameByFileContent, PintHeader):
pass
class ParsedProjecHeader(fc.NameByHashIter, PintHeader):
@classmethod
def from_parsed_project(cls, pp: fp.ParsedProject, reader_id):
tmp = (
f"{stmt.content_hash.algorithm_name}:{stmt.content_hash.hexdigest}"
for stmt in pp.iter_statements()
if isinstance(stmt, fp.BOS)
)
return cls(tuple(tmp), reader_id)
class PintDiskCache(fc.DiskCache):
_header_classes = {
pathlib.Path: PathHeader,
str: PathHeader.from_string,
fp.ParsedProject: ParsedProjecHeader.from_parsed_project,
}
return PintDiskCache
|