cognite-neat 0.109.4__py3-none-any.whl → 0.110.0__py3-none-any.whl
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.
Potentially problematic release.
This version of cognite-neat might be problematic. Click here for more details.
- cognite/neat/_alpha.py +2 -0
- cognite/neat/_client/_api/schema.py +17 -1
- cognite/neat/_client/data_classes/schema.py +3 -3
- cognite/neat/_constants.py +11 -0
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +9 -10
- cognite/neat/_graph/extractors/_iodd.py +3 -3
- cognite/neat/_graph/extractors/_mock_graph_generator.py +9 -7
- cognite/neat/_graph/loaders/_rdf2dms.py +285 -346
- cognite/neat/_graph/queries/_base.py +28 -92
- cognite/neat/_graph/transformers/__init__.py +1 -3
- cognite/neat/_graph/transformers/_rdfpath.py +2 -49
- cognite/neat/_issues/__init__.py +1 -6
- cognite/neat/_issues/_base.py +21 -252
- cognite/neat/_issues/_contextmanagers.py +46 -0
- cognite/neat/_issues/_factory.py +61 -0
- cognite/neat/_issues/errors/__init__.py +18 -4
- cognite/neat/_issues/errors/_wrapper.py +81 -3
- cognite/neat/_issues/formatters.py +4 -4
- cognite/neat/_issues/warnings/__init__.py +3 -2
- cognite/neat/_issues/warnings/_properties.py +8 -0
- cognite/neat/_rules/_constants.py +9 -0
- cognite/neat/_rules/_shared.py +3 -2
- cognite/neat/_rules/analysis/__init__.py +2 -3
- cognite/neat/_rules/analysis/_base.py +450 -258
- cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
- cognite/neat/_rules/exporters/_rules2excel.py +2 -8
- cognite/neat/_rules/exporters/_rules2instance_template.py +2 -2
- cognite/neat/_rules/exporters/_rules2ontology.py +5 -4
- cognite/neat/_rules/importers/_base.py +2 -47
- cognite/neat/_rules/importers/_dms2rules.py +7 -10
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +2 -2
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +59 -25
- cognite/neat/_rules/importers/_rdf/_shared.py +1 -1
- cognite/neat/_rules/importers/_spreadsheet2rules.py +12 -9
- cognite/neat/_rules/models/dms/_rules.py +3 -1
- cognite/neat/_rules/models/dms/_rules_input.py +4 -0
- cognite/neat/_rules/models/dms/_validation.py +14 -4
- cognite/neat/_rules/models/entities/_loaders.py +1 -1
- cognite/neat/_rules/models/entities/_multi_value.py +2 -2
- cognite/neat/_rules/models/information/_rules.py +18 -17
- cognite/neat/_rules/models/information/_rules_input.py +2 -1
- cognite/neat/_rules/models/information/_validation.py +3 -1
- cognite/neat/_rules/transformers/__init__.py +8 -2
- cognite/neat/_rules/transformers/_converters.py +228 -43
- cognite/neat/_rules/transformers/_verification.py +5 -10
- cognite/neat/_session/_base.py +4 -4
- cognite/neat/_session/_prepare.py +12 -0
- cognite/neat/_session/_read.py +21 -17
- cognite/neat/_session/_show.py +11 -123
- cognite/neat/_session/_state.py +0 -2
- cognite/neat/_session/_subset.py +64 -0
- cognite/neat/_session/_to.py +63 -12
- cognite/neat/_store/_graph_store.py +5 -246
- cognite/neat/_utils/rdf_.py +2 -2
- cognite/neat/_utils/spreadsheet.py +44 -1
- cognite/neat/_utils/text.py +51 -32
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/RECORD +62 -64
- {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/WHEEL +1 -1
- cognite/neat/_graph/queries/_construct.py +0 -187
- cognite/neat/_graph/queries/_shared.py +0 -173
- cognite/neat/_rules/analysis/_dms.py +0 -57
- cognite/neat/_rules/analysis/_information.py +0 -249
- cognite/neat/_rules/models/_rdfpath.py +0 -372
- {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from collections.abc import Iterator
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
|
|
7
|
+
from cognite.neat._utils.spreadsheet import SpreadsheetRead
|
|
8
|
+
|
|
9
|
+
from ._base import IssueList, MultiValueError, NeatError
|
|
10
|
+
from ._factory import from_pydantic_errors, from_warning
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@contextmanager
|
|
14
|
+
def catch_warnings() -> Iterator[IssueList]:
|
|
15
|
+
"""Catch warnings and append them to the issues list."""
|
|
16
|
+
issues = IssueList()
|
|
17
|
+
with warnings.catch_warnings(record=True) as warning_logger:
|
|
18
|
+
warnings.simplefilter("always")
|
|
19
|
+
try:
|
|
20
|
+
yield issues
|
|
21
|
+
finally:
|
|
22
|
+
if warning_logger:
|
|
23
|
+
issues.extend([from_warning(warning) for warning in warning_logger])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@contextmanager
|
|
27
|
+
def catch_issues(read_info_by_sheet: dict[str, SpreadsheetRead] | None = None) -> Iterator[IssueList]:
|
|
28
|
+
"""This is an internal help function to handle issues and warnings.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
read_info_by_sheet (dict[str, SpreadsheetRead]): The read information by sheet. This is used to adjust
|
|
32
|
+
the row numbers in the errors/warnings.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
IssueList: The list of issues.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
with catch_warnings() as issues:
|
|
39
|
+
try:
|
|
40
|
+
yield issues
|
|
41
|
+
except ValidationError as e:
|
|
42
|
+
issues.extend(from_pydantic_errors(e.errors(), read_info_by_sheet))
|
|
43
|
+
except NeatError as single:
|
|
44
|
+
issues.append(single)
|
|
45
|
+
except MultiValueError as multi:
|
|
46
|
+
issues.extend(multi.errors)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
from warnings import WarningMessage
|
|
3
|
+
|
|
4
|
+
from pydantic_core import ErrorDetails
|
|
5
|
+
|
|
6
|
+
from cognite.neat._issues._base import NeatError, NeatWarning
|
|
7
|
+
from cognite.neat._utils.spreadsheet import SpreadsheetRead
|
|
8
|
+
|
|
9
|
+
from .errors import NeatValueError, SpreadsheetError
|
|
10
|
+
from .warnings import NeatValueWarning
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def from_pydantic_errors(
|
|
14
|
+
errors: list[ErrorDetails], read_info_by_sheet: dict[str, SpreadsheetRead] | None = None
|
|
15
|
+
) -> list[NeatError]:
|
|
16
|
+
read_info_by_sheet = read_info_by_sheet or {}
|
|
17
|
+
return [
|
|
18
|
+
_from_pydantic_error(error, read_info_by_sheet)
|
|
19
|
+
for error in errors
|
|
20
|
+
# Skip the error for SheetList, as it is not relevant for the user. This is an
|
|
21
|
+
# internal class used to have helper methods for a lists as .to_pandas()
|
|
22
|
+
if not (error["type"] == "is_instance_of" and error["loc"][1] == "is-instance[SheetList]")
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def from_warning(warning: WarningMessage) -> NeatWarning:
|
|
27
|
+
if isinstance(warning.message, NeatWarning):
|
|
28
|
+
return warning.message
|
|
29
|
+
message = f"{warning.category.__name__}: {warning.message!s}"
|
|
30
|
+
if warning.source:
|
|
31
|
+
message += f" Source: {warning.source}"
|
|
32
|
+
return NeatValueWarning(message)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _from_pydantic_error(error: ErrorDetails, read_info_by_sheet: dict[str, SpreadsheetRead]) -> NeatError:
|
|
36
|
+
neat_error = _create_neat_value_error(error)
|
|
37
|
+
location = error["loc"]
|
|
38
|
+
return SpreadsheetError.create(location, neat_error, read_info_by_sheet.get(cast(str, location[0])))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _create_neat_value_error(error: ErrorDetails) -> NeatValueError:
|
|
42
|
+
if (ctx := error.get("ctx")) and (neat_error := ctx.get("error")) and isinstance(neat_error, NeatError):
|
|
43
|
+
# Is already a NeatError
|
|
44
|
+
return neat_error
|
|
45
|
+
return _pydantic_to_neat_error(error)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _pydantic_to_neat_error(error: ErrorDetails) -> NeatValueError:
|
|
49
|
+
error_type = error["type"]
|
|
50
|
+
input_value = error["input"]
|
|
51
|
+
match error_type:
|
|
52
|
+
# See https://docs.pydantic.dev/latest/errors/validation_errors/ for all possible error types:
|
|
53
|
+
case error_type if error_type.endswith("_type") | error_type.endswith("_parsing"):
|
|
54
|
+
if input_value is None:
|
|
55
|
+
return NeatValueError("value is missing.")
|
|
56
|
+
expected_type = error_type.removesuffix("_type").removesuffix("_parsing")
|
|
57
|
+
return NeatValueError(f"Expected a {expected_type} type, got {input_value!r}")
|
|
58
|
+
case _:
|
|
59
|
+
# The above cases overwrite the human-readable message from pydantic.
|
|
60
|
+
# Motivation for overwriting is that pydantic is developer-oriented and while neat is SME-oriented.
|
|
61
|
+
return NeatValueError(f"{error['msg']} got '{input_value}'")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from cognite.neat._issues._base import
|
|
1
|
+
from cognite.neat._issues._base import NeatError, _get_subclasses
|
|
2
2
|
|
|
3
3
|
from ._external import (
|
|
4
4
|
AuthorizationError,
|
|
@@ -31,12 +31,23 @@ from ._resources import (
|
|
|
31
31
|
ResourceNotFoundError,
|
|
32
32
|
ResourceRetrievalError,
|
|
33
33
|
)
|
|
34
|
-
from ._wrapper import
|
|
34
|
+
from ._wrapper import (
|
|
35
|
+
ClassValueError,
|
|
36
|
+
ContainerValueError,
|
|
37
|
+
EnumValueError,
|
|
38
|
+
MetadataValueError,
|
|
39
|
+
NodeValueError,
|
|
40
|
+
PropertyValueError,
|
|
41
|
+
SpreadsheetError,
|
|
42
|
+
ViewValueError,
|
|
43
|
+
)
|
|
35
44
|
|
|
36
45
|
__all__ = [
|
|
37
46
|
"AuthorizationError",
|
|
38
47
|
"CDFMissingClientError",
|
|
39
|
-
"
|
|
48
|
+
"ClassValueError",
|
|
49
|
+
"ContainerValueError",
|
|
50
|
+
"EnumValueError",
|
|
40
51
|
"FileMissingRequiredFieldError",
|
|
41
52
|
"FileNotAFileError",
|
|
42
53
|
"FileNotFoundNeatError",
|
|
@@ -48,12 +59,14 @@ __all__ = [
|
|
|
48
59
|
"NeatTypeError",
|
|
49
60
|
"NeatValueError",
|
|
50
61
|
"NeatYamlError",
|
|
62
|
+
"NodeValueError",
|
|
51
63
|
"OxigraphStorageLockedError",
|
|
52
64
|
"PropertyDefinitionDuplicatedError",
|
|
53
65
|
"PropertyDefinitionError",
|
|
54
66
|
"PropertyMappingDuplicatedError",
|
|
55
67
|
"PropertyNotFoundError",
|
|
56
68
|
"PropertyTypeNotSupportedError",
|
|
69
|
+
"PropertyValueError",
|
|
57
70
|
"RegexViolationError",
|
|
58
71
|
"ResourceChangedError",
|
|
59
72
|
"ResourceConversionError",
|
|
@@ -65,7 +78,8 @@ __all__ = [
|
|
|
65
78
|
"ResourceNotFoundError",
|
|
66
79
|
"ResourceRetrievalError",
|
|
67
80
|
"ReversedConnectionNotFeasibleError",
|
|
68
|
-
"
|
|
81
|
+
"SpreadsheetError",
|
|
82
|
+
"ViewValueError",
|
|
69
83
|
]
|
|
70
84
|
|
|
71
85
|
_NEAT_ERRORS_BY_NAME = {error.__name__: error for error in _get_subclasses(NeatError, include_base=True)}
|
|
@@ -1,11 +1,89 @@
|
|
|
1
|
+
from abc import ABC
|
|
1
2
|
from dataclasses import dataclass
|
|
3
|
+
from typing import ClassVar, cast
|
|
2
4
|
|
|
3
5
|
from cognite.neat._issues import NeatError
|
|
6
|
+
from cognite.neat._utils.spreadsheet import SpreadsheetRead
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
@dataclass(unsafe_hash=True)
|
|
7
|
-
class
|
|
8
|
-
"""
|
|
10
|
+
class SpreadsheetError(NeatError, ValueError, ABC):
|
|
11
|
+
"""In row {row}: {error}"""
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
_name: ClassVar[str] = ""
|
|
11
14
|
error: NeatError
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def create(
|
|
18
|
+
cls, location: tuple[int | str, ...], error: NeatError, spreadsheet: SpreadsheetRead | None = None
|
|
19
|
+
) -> "SpreadsheetError":
|
|
20
|
+
spreadsheet_name = cast(str, location[0])
|
|
21
|
+
if spreadsheet_name not in ERROR_CLS_BY_SPREADSHEET_NAME:
|
|
22
|
+
# This happens for the metadata sheet, which are individual fields
|
|
23
|
+
return MetadataValueError(error, field_name=spreadsheet_name)
|
|
24
|
+
|
|
25
|
+
error_cls = ERROR_CLS_BY_SPREADSHEET_NAME[spreadsheet_name]
|
|
26
|
+
row, column = cast(tuple[int, str], location[2:4])
|
|
27
|
+
|
|
28
|
+
if spreadsheet:
|
|
29
|
+
row = spreadsheet.adjusted_row_number(row)
|
|
30
|
+
|
|
31
|
+
return error_cls(
|
|
32
|
+
row=row,
|
|
33
|
+
error=error,
|
|
34
|
+
column=column,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(unsafe_hash=True)
|
|
39
|
+
class SpreadsheetListError(SpreadsheetError, ABC):
|
|
40
|
+
"""In row {row}, column '{column}': {error}"""
|
|
41
|
+
|
|
42
|
+
row: int
|
|
43
|
+
column: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(unsafe_hash=True)
|
|
47
|
+
class MetadataValueError(SpreadsheetError):
|
|
48
|
+
"""In field {field_name}: {error}"""
|
|
49
|
+
|
|
50
|
+
_type: ClassVar[str] = "Metadata"
|
|
51
|
+
field_name: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(unsafe_hash=True)
|
|
55
|
+
class ViewValueError(SpreadsheetListError):
|
|
56
|
+
_name = "Views"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(unsafe_hash=True)
|
|
60
|
+
class ContainerValueError(SpreadsheetListError):
|
|
61
|
+
_name = "Containers"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(unsafe_hash=True)
|
|
65
|
+
class PropertyValueError(SpreadsheetListError):
|
|
66
|
+
_name = "Properties"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(unsafe_hash=True)
|
|
70
|
+
class ClassValueError(SpreadsheetListError):
|
|
71
|
+
_name = "Classes"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(unsafe_hash=True)
|
|
75
|
+
class EnumValueError(SpreadsheetListError):
|
|
76
|
+
_name = "Enum"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(unsafe_hash=True)
|
|
80
|
+
class NodeValueError(SpreadsheetListError):
|
|
81
|
+
_name = "Nodes"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
ERROR_CLS_BY_SPREADSHEET_NAME = {cls_._name: cls_ for cls_ in SpreadsheetListError.__subclasses__()}
|
|
85
|
+
|
|
86
|
+
# Efficient way to set docstring for all classes
|
|
87
|
+
for _cls in ERROR_CLS_BY_SPREADSHEET_NAME.values():
|
|
88
|
+
_cls.__doc__ = SpreadsheetListError.__doc__
|
|
89
|
+
del _cls
|
|
@@ -3,7 +3,7 @@ import xml.etree.ElementTree as ET
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from ._base import
|
|
6
|
+
from ._base import IssueList, NeatError, NeatWarning
|
|
7
7
|
|
|
8
8
|
__all__ = ["FORMATTER_BY_NAME", "BasicHTML", "Formatter"]
|
|
9
9
|
|
|
@@ -13,14 +13,14 @@ class Formatter(ABC):
|
|
|
13
13
|
default_file_prefix: str = "validation_report"
|
|
14
14
|
|
|
15
15
|
@abstractmethod
|
|
16
|
-
def create_report(self, issues:
|
|
16
|
+
def create_report(self, issues: IssueList) -> str:
|
|
17
17
|
raise NotImplementedError()
|
|
18
18
|
|
|
19
19
|
@property
|
|
20
20
|
def default_file_name(self) -> str:
|
|
21
21
|
return f"{self.default_file_prefix}_{type(self).__name__.lower()}{self.file_suffix}"
|
|
22
22
|
|
|
23
|
-
def write_to_file(self, issues:
|
|
23
|
+
def write_to_file(self, issues: IssueList, file_or_dir_path: Path | None = None) -> None:
|
|
24
24
|
if file_or_dir_path is None:
|
|
25
25
|
file_or_dir_path = Path(self.default_file_name)
|
|
26
26
|
elif file_or_dir_path.is_dir():
|
|
@@ -41,7 +41,7 @@ class BasicHTML(Formatter):
|
|
|
41
41
|
self._doc = ET.Element("html")
|
|
42
42
|
self._body = ET.SubElement(self._doc, "body")
|
|
43
43
|
|
|
44
|
-
def create_report(self, issues:
|
|
44
|
+
def create_report(self, issues: IssueList) -> str:
|
|
45
45
|
errors = [issue for issue in issues if isinstance(issue, NeatError)]
|
|
46
46
|
warnings_ = [issue for issue in issues if isinstance(issue, NeatWarning)]
|
|
47
47
|
self._doc.clear()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
conflicts with the built-in Python warnings module. However, it is expected to always be used in an absolute
|
|
3
3
|
import, and should thus not cause a naming conflict."""
|
|
4
4
|
|
|
5
|
-
from cognite.neat._issues._base import
|
|
5
|
+
from cognite.neat._issues._base import NeatWarning, _get_subclasses
|
|
6
6
|
|
|
7
7
|
from . import user_modeling
|
|
8
8
|
from ._external import (
|
|
@@ -29,6 +29,7 @@ from ._properties import (
|
|
|
29
29
|
PropertyDataTypeConversionWarning,
|
|
30
30
|
PropertyDefinitionDuplicatedWarning,
|
|
31
31
|
PropertyDirectRelationLimitWarning,
|
|
32
|
+
PropertyMultipleValueWarning,
|
|
32
33
|
PropertyNotFoundWarning,
|
|
33
34
|
PropertyOverwritingWarning,
|
|
34
35
|
PropertyTypeNotSupportedWarning,
|
|
@@ -49,7 +50,6 @@ __all__ = [
|
|
|
49
50
|
"CDFAuthWarning",
|
|
50
51
|
"CDFMaxIterationsWarning",
|
|
51
52
|
"CDFNotSupportedWarning",
|
|
52
|
-
"DefaultWarning",
|
|
53
53
|
"FileItemNotSupportedWarning",
|
|
54
54
|
"FileMissingRequiredFieldWarning",
|
|
55
55
|
"FileReadWarning",
|
|
@@ -65,6 +65,7 @@ __all__ = [
|
|
|
65
65
|
"PropertyDataTypeConversionWarning",
|
|
66
66
|
"PropertyDefinitionDuplicatedWarning",
|
|
67
67
|
"PropertyDirectRelationLimitWarning",
|
|
68
|
+
"PropertyMultipleValueWarning",
|
|
68
69
|
"PropertyNotFoundWarning",
|
|
69
70
|
"PropertyOverwritingWarning",
|
|
70
71
|
"PropertyTypeNotSupportedWarning",
|
|
@@ -80,3 +80,11 @@ class PropertyDirectRelationLimitWarning(PropertyWarning[T_Identifier]):
|
|
|
80
80
|
resource_type = "view"
|
|
81
81
|
|
|
82
82
|
limit: int = DMS_DIRECT_RELATION_LIST_LIMIT
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(unsafe_hash=True)
|
|
86
|
+
class PropertyMultipleValueWarning(PropertyWarning[T_Identifier]):
|
|
87
|
+
"""The {resource_type} with identifier {identifier} has a property {property_name} with multiple values.
|
|
88
|
+
Selecting the first value {value}, the rest will be ignored."""
|
|
89
|
+
|
|
90
|
+
value: str
|
cognite/neat/_rules/_shared.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Generic, TypeAlias, TypeVar
|
|
3
3
|
|
|
4
4
|
from cognite.neat._rules.models import (
|
|
5
5
|
DMSRules,
|
|
@@ -7,6 +7,7 @@ from cognite.neat._rules.models import (
|
|
|
7
7
|
)
|
|
8
8
|
from cognite.neat._rules.models.dms._rules_input import DMSInputRules
|
|
9
9
|
from cognite.neat._rules.models.information._rules_input import InformationInputRules
|
|
10
|
+
from cognite.neat._utils.spreadsheet import SpreadsheetRead
|
|
10
11
|
|
|
11
12
|
VerifiedRules: TypeAlias = InformationRules | DMSRules
|
|
12
13
|
|
|
@@ -20,7 +21,7 @@ class ReadRules(Generic[T_InputRules]):
|
|
|
20
21
|
"""This represents a rules that has been read."""
|
|
21
22
|
|
|
22
23
|
rules: T_InputRules | None
|
|
23
|
-
read_context: dict[str,
|
|
24
|
+
read_context: dict[str, SpreadsheetRead]
|
|
24
25
|
|
|
25
26
|
@classmethod
|
|
26
27
|
def display_type_name(cls) -> str:
|