infinity-data 1.0.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.
- infinity_data/__init__.py +41 -0
- infinity_data/emit/__init__.py +9 -0
- infinity_data/emit/converter.py +55 -0
- infinity_data/frontend.py +37 -0
- infinity_data/infra/__init__.py +4 -0
- infinity_data/infra/diagnostics.py +212 -0
- infinity_data/infra/file.py +83 -0
- infinity_data/infra/ll1_stream.py +71 -0
- infinity_data/infra/location.py +80 -0
- infinity_data/infra/path.py +46 -0
- infinity_data/parser/__init__.py +79 -0
- infinity_data/parser/diagnostics.py +92 -0
- infinity_data/parser/models.py +295 -0
- infinity_data/parser/parser.py +988 -0
- infinity_data/parser/token_stream.py +128 -0
- infinity_data/pipeline.py +252 -0
- infinity_data/sandbox/__init__.py +32 -0
- infinity_data/sandbox/config.py +61 -0
- infinity_data/sandbox/errors.py +106 -0
- infinity_data/sandbox/mediator.py +214 -0
- infinity_data/sandbox/schema.py +21 -0
- infinity_data/semantic/__init__.py +59 -0
- infinity_data/semantic/builder/__init__.py +29 -0
- infinity_data/semantic/builder/builder.py +523 -0
- infinity_data/semantic/builder/models.py +163 -0
- infinity_data/semantic/constraints.py +163 -0
- infinity_data/semantic/diagnostics.py +172 -0
- infinity_data/semantic/executor/__init__.py +10 -0
- infinity_data/semantic/executor/executor.py +259 -0
- infinity_data/semantic/registry/__init__.py +168 -0
- infinity_data/semantic/registry/_core.py +212 -0
- infinity_data/semantic/registry/dict_constraints.py +56 -0
- infinity_data/semantic/registry/general.py +362 -0
- infinity_data/semantic/registry/logic.py +134 -0
- infinity_data/semantic/registry/types.py +136 -0
- infinity_data/semantic/resolver/__init__.py +20 -0
- infinity_data/semantic/resolver/imports.py +191 -0
- infinity_data/semantic/resolver/models.py +67 -0
- infinity_data/semantic/resolver/resolver.py +345 -0
- infinity_data/tokenizer/__init__.py +16 -0
- infinity_data/tokenizer/char_stream.py +72 -0
- infinity_data/tokenizer/diagnostics.py +62 -0
- infinity_data/tokenizer/finalizer.py +271 -0
- infinity_data/tokenizer/models/__init__.py +17 -0
- infinity_data/tokenizer/models/raw_tokens.py +61 -0
- infinity_data/tokenizer/models/tokens.py +245 -0
- infinity_data/tokenizer/tokenizer.py +531 -0
- infinity_data-1.0.0.dist-info/METADATA +60 -0
- infinity_data-1.0.0.dist-info/RECORD +52 -0
- infinity_data-1.0.0.dist-info/WHEEL +5 -0
- infinity_data-1.0.0.dist-info/licenses/LICENSE +21 -0
- infinity_data-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""约束注册表:内置约束 + 用户自定义(模板即约束)。
|
|
2
|
+
|
|
3
|
+
- 表驱动:每个约束登记 ``(name, fn, min_args, max_args, description)``
|
|
4
|
+
- 内置约束按类别拆分子模块:types / general / dict_constraints / logic,
|
|
5
|
+
注册表在此组装(单注册表 = 唯一执行入口)
|
|
6
|
+
- ``executor`` 回调支持嵌套约束:``each`` / ``not`` / ``any`` / ``one`` /
|
|
7
|
+
``all`` / ``when`` / ``field`` / 模板约束
|
|
8
|
+
- M5 将在 :class:`ConstraintEntry` 上追加 json_schema 生成回调,
|
|
9
|
+
实现「模板即约束 → JSON Schema」的单注册表复用
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from infinity_data.semantic.builder.models import ResolvedConstraint, StdValue
|
|
15
|
+
from infinity_data.semantic.registry._core import (
|
|
16
|
+
ConstraintEntry,
|
|
17
|
+
ConstraintFn,
|
|
18
|
+
ConstraintResult,
|
|
19
|
+
Executor,
|
|
20
|
+
describe,
|
|
21
|
+
fail_result,
|
|
22
|
+
ok_result,
|
|
23
|
+
)
|
|
24
|
+
from infinity_data.semantic.registry.dict_constraints import _check_field, _check_has
|
|
25
|
+
from infinity_data.semantic.registry.general import (
|
|
26
|
+
_check_each,
|
|
27
|
+
_check_email,
|
|
28
|
+
_check_eq,
|
|
29
|
+
_check_hostname,
|
|
30
|
+
_check_in,
|
|
31
|
+
_check_ip,
|
|
32
|
+
_check_ip4,
|
|
33
|
+
_check_ip6,
|
|
34
|
+
_check_negative,
|
|
35
|
+
_check_nonnegative,
|
|
36
|
+
_check_positive,
|
|
37
|
+
_check_range,
|
|
38
|
+
_check_regex,
|
|
39
|
+
_check_size,
|
|
40
|
+
_check_unique,
|
|
41
|
+
_check_url,
|
|
42
|
+
_check_uuid,
|
|
43
|
+
)
|
|
44
|
+
from infinity_data.semantic.registry.logic import (
|
|
45
|
+
_check_all,
|
|
46
|
+
_check_any,
|
|
47
|
+
_check_not,
|
|
48
|
+
_check_one,
|
|
49
|
+
_check_when,
|
|
50
|
+
)
|
|
51
|
+
from infinity_data.semantic.registry.types import (
|
|
52
|
+
_check_bool,
|
|
53
|
+
_check_dict,
|
|
54
|
+
_check_float,
|
|
55
|
+
_check_int,
|
|
56
|
+
_check_list,
|
|
57
|
+
_check_nullable,
|
|
58
|
+
_check_object,
|
|
59
|
+
_check_str,
|
|
60
|
+
)
|
|
61
|
+
from infinity_data.tokenizer.models.raw_tokens import SourceRange
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
'ConstraintRegistry',
|
|
65
|
+
'ConstraintResult',
|
|
66
|
+
'ConstraintEntry',
|
|
67
|
+
'ConstraintFn',
|
|
68
|
+
'Executor',
|
|
69
|
+
'ok_result',
|
|
70
|
+
'fail_result',
|
|
71
|
+
'describe',
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ConstraintRegistry:
|
|
76
|
+
"""约束注册表:内置约束 + 用户自定义(模板即约束)。"""
|
|
77
|
+
|
|
78
|
+
def __init__(self) -> None:
|
|
79
|
+
self._entries: dict[str, ConstraintEntry] = {}
|
|
80
|
+
self._register_builtins()
|
|
81
|
+
|
|
82
|
+
def register(
|
|
83
|
+
self,
|
|
84
|
+
name: str,
|
|
85
|
+
fn: ConstraintFn,
|
|
86
|
+
*,
|
|
87
|
+
min_args: int = 0,
|
|
88
|
+
max_args: int | None = None,
|
|
89
|
+
description: str = '',
|
|
90
|
+
) -> None:
|
|
91
|
+
self._entries[name] = ConstraintEntry(
|
|
92
|
+
name=name,
|
|
93
|
+
fn=fn,
|
|
94
|
+
min_args=min_args,
|
|
95
|
+
max_args=max_args,
|
|
96
|
+
description=description,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def lookup(self, name: str) -> ConstraintEntry | None:
|
|
100
|
+
return self._entries.get(name)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def names(self) -> list[str]:
|
|
104
|
+
return list(self._entries)
|
|
105
|
+
|
|
106
|
+
def apply(
|
|
107
|
+
self,
|
|
108
|
+
constraint: ResolvedConstraint,
|
|
109
|
+
value: StdValue | None,
|
|
110
|
+
source: SourceRange | None,
|
|
111
|
+
path: str,
|
|
112
|
+
executor: Executor,
|
|
113
|
+
) -> ConstraintResult:
|
|
114
|
+
"""按名称执行约束(含参数个数校验)。"""
|
|
115
|
+
entry = self._entries.get(constraint.name)
|
|
116
|
+
if entry is None:
|
|
117
|
+
return fail_result('constraint.unknown', {'name': constraint.name}, source, path)
|
|
118
|
+
n = len(constraint.args)
|
|
119
|
+
if n < entry.min_args or (entry.max_args is not None and n > entry.max_args):
|
|
120
|
+
expected = str(entry.min_args) if entry.max_args is None else f'{entry.min_args}~{entry.max_args}'
|
|
121
|
+
return fail_result(
|
|
122
|
+
'constraint.arg_count', {'name': constraint.name, 'expected': expected, 'given': n}, source, path
|
|
123
|
+
)
|
|
124
|
+
return entry.fn(value, source, path, constraint.args, executor)
|
|
125
|
+
|
|
126
|
+
def _register_builtins(self) -> None:
|
|
127
|
+
"""注册 neo_desg.md §1.2 全部内置约束。"""
|
|
128
|
+
# ── 类型约束 ──────────────────────────────
|
|
129
|
+
self.register('object', _check_object, description='通用超类型,总是通过')
|
|
130
|
+
self.register('?', _check_nullable, description='纯可空类型:noexist 或 null')
|
|
131
|
+
self.register('int', _check_int, description='有符号整数')
|
|
132
|
+
self.register('float', _check_float, description='十进制浮点(只接受 float,不含 int 提升)')
|
|
133
|
+
self.register('str', _check_str, description='utf-8 字符串')
|
|
134
|
+
self.register('bool', _check_bool, description='布尔值')
|
|
135
|
+
self.register('list', _check_list, description='数组')
|
|
136
|
+
self.register('dict', _check_dict, description='字典')
|
|
137
|
+
|
|
138
|
+
# ── 一般约束 ──────────────────────────────
|
|
139
|
+
self.register('range', _check_range, min_args=1, max_args=2, description='数值范围 range(ge[, le]),可省略一端')
|
|
140
|
+
self.register('size', _check_size, min_args=1, max_args=2, description='集合大小或字符串长度 size(ge[, le])')
|
|
141
|
+
self.register('each', _check_each, min_args=1, max_args=1, description='每个元素均满足 each(constraint)')
|
|
142
|
+
self.register('in', _check_in, min_args=1, description='值在给定选项中 in(choice, ...)')
|
|
143
|
+
self.register('ip', _check_ip, description='IPv4 或 IPv6 地址')
|
|
144
|
+
self.register('ip4', _check_ip4, description='IPv4 地址')
|
|
145
|
+
self.register('ip6', _check_ip6, description='IPv6 地址')
|
|
146
|
+
self.register('regex', _check_regex, min_args=1, max_args=1, description='正则全匹配 regex("re")')
|
|
147
|
+
self.register('email', _check_email, description='邮箱格式')
|
|
148
|
+
self.register('url', _check_url, description='URL 格式')
|
|
149
|
+
self.register('uuid', _check_uuid, description='UUID 格式')
|
|
150
|
+
self.register('hostname', _check_hostname, description='主机名格式')
|
|
151
|
+
self.register('positive', _check_positive, description='正数 (> 0)')
|
|
152
|
+
self.register('negative', _check_negative, description='负数 (< 0)')
|
|
153
|
+
self.register('nonnegative', _check_nonnegative, description='非负数 (>= 0)')
|
|
154
|
+
self.register('eq', _check_eq, min_args=1, max_args=1, description='等于指定值 eq(value)')
|
|
155
|
+
self.register('unique', _check_unique, description='集合元素不重复')
|
|
156
|
+
|
|
157
|
+
# ── 字典约束 ──────────────────────────────
|
|
158
|
+
self.register('has', _check_has, min_args=1, max_args=1, description='包含指定键 has(key)')
|
|
159
|
+
self.register('field', _check_field, min_args=2, max_args=2, description='字段约束 field(name, constraint)')
|
|
160
|
+
|
|
161
|
+
# ── 逻辑约束 ──────────────────────────────
|
|
162
|
+
self.register('not', _check_not, min_args=1, max_args=1, description='内部约束不满足则满足')
|
|
163
|
+
self.register('any', _check_any, min_args=1, description='任意子约束满足')
|
|
164
|
+
self.register('one', _check_one, min_args=1, description='恰好一个子约束满足')
|
|
165
|
+
self.register('all', _check_all, min_args=1, description='全部子约束满足')
|
|
166
|
+
self.register(
|
|
167
|
+
'when', _check_when, min_args=2, max_args=2, description='条件满足时要求另一约束满足 when(cond, req)'
|
|
168
|
+
)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""约束核心类型与共享辅助(注册表包的内部基础层)。
|
|
2
|
+
|
|
3
|
+
- 执行结果 / 嵌套回调协议 / 登记项
|
|
4
|
+
- 人类可读描述与数值、嵌套参数辅助
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable, Mapping
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from decimal import Decimal, InvalidOperation
|
|
12
|
+
from typing import Any, Protocol, cast
|
|
13
|
+
|
|
14
|
+
from infinity_data.infra.diagnostics import Diagnostic, Severity
|
|
15
|
+
from infinity_data.semantic.builder.models import (
|
|
16
|
+
ResolvedConstraint,
|
|
17
|
+
StdArray,
|
|
18
|
+
StdField,
|
|
19
|
+
StdLiteral,
|
|
20
|
+
StdObject,
|
|
21
|
+
StdValue,
|
|
22
|
+
)
|
|
23
|
+
from infinity_data.tokenizer.models.raw_tokens import SourceRange
|
|
24
|
+
|
|
25
|
+
# ═══════════════════════════════════════════════════════════
|
|
26
|
+
# 基础类型
|
|
27
|
+
# ═══════════════════════════════════════════════════════════
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ConstraintResult:
|
|
32
|
+
"""约束执行结果(只校验,不转换值)。"""
|
|
33
|
+
|
|
34
|
+
ok: bool
|
|
35
|
+
diagnostics: list[Diagnostic] = field(default_factory=list[Diagnostic])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Executor(Protocol):
|
|
39
|
+
"""嵌套约束执行回调(由 ConstraintExecutor 提供)。"""
|
|
40
|
+
|
|
41
|
+
def __call__(
|
|
42
|
+
self,
|
|
43
|
+
constraint: ResolvedConstraint,
|
|
44
|
+
value: StdValue | None,
|
|
45
|
+
source: SourceRange | None,
|
|
46
|
+
path: str,
|
|
47
|
+
) -> ConstraintResult: ...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
ConstraintFn = Callable[
|
|
51
|
+
[StdValue | None, SourceRange | None, str, list[Any], Executor],
|
|
52
|
+
ConstraintResult,
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class ConstraintEntry:
|
|
58
|
+
"""约束登记项。"""
|
|
59
|
+
|
|
60
|
+
name: str
|
|
61
|
+
fn: ConstraintFn
|
|
62
|
+
min_args: int = 0
|
|
63
|
+
max_args: int | None = None
|
|
64
|
+
description: str = ''
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def ok_result() -> ConstraintResult:
|
|
68
|
+
"""构造通过结果。"""
|
|
69
|
+
return ConstraintResult(ok=True)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def fail_result(
|
|
73
|
+
code: str,
|
|
74
|
+
params: Mapping[str, Any],
|
|
75
|
+
source: SourceRange | None,
|
|
76
|
+
path: str,
|
|
77
|
+
) -> ConstraintResult:
|
|
78
|
+
"""构造结构化失败结果(code + params,message 由注册表渲染)。"""
|
|
79
|
+
return ConstraintResult(
|
|
80
|
+
ok=False,
|
|
81
|
+
diagnostics=[
|
|
82
|
+
Diagnostic(severity=Severity.ERROR, code=code, params=dict(params), source=source, path=path),
|
|
83
|
+
],
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ═══════════════════════════════════════════════════════════
|
|
88
|
+
# 共享辅助
|
|
89
|
+
# ═══════════════════════════════════════════════════════════
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def describe(val: StdValue | None) -> str:
|
|
93
|
+
"""人类可读的值描述。"""
|
|
94
|
+
if val is None:
|
|
95
|
+
return '无值'
|
|
96
|
+
if isinstance(val, StdLiteral):
|
|
97
|
+
return f'{val.kind}({val.value!r})'
|
|
98
|
+
if isinstance(val, StdArray):
|
|
99
|
+
return f'list[{len(val.elements)}]'
|
|
100
|
+
return f'dict[{len(val.fields)}]'
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def as_number(val: StdValue | None) -> Decimal | None:
|
|
104
|
+
"""字面量 → 数值(int/float kind 才有效)。"""
|
|
105
|
+
if isinstance(val, StdLiteral) and val.kind in ('int', 'float'):
|
|
106
|
+
v = val.value
|
|
107
|
+
if isinstance(v, int):
|
|
108
|
+
return Decimal(v)
|
|
109
|
+
if isinstance(v, Decimal):
|
|
110
|
+
return v
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def as_str(val: StdValue | None) -> str | None:
|
|
115
|
+
"""字面量 → 字符串(str kind 才有效)。"""
|
|
116
|
+
if isinstance(val, StdLiteral) and val.kind == 'str':
|
|
117
|
+
v = val.value
|
|
118
|
+
return v if isinstance(v, str) else None
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def as_decimal_arg(a: Any) -> Decimal | None:
|
|
123
|
+
"""约束参数 → Decimal(int / Decimal 才有效;NaN 视为无效参数)。"""
|
|
124
|
+
if isinstance(a, bool):
|
|
125
|
+
return None
|
|
126
|
+
if isinstance(a, int):
|
|
127
|
+
return Decimal(a)
|
|
128
|
+
if isinstance(a, Decimal):
|
|
129
|
+
# Decimal 比较遇 NaN 会抛 InvalidOperation(默认 trap),此处拒绝 NaN 参数
|
|
130
|
+
return None if a.is_nan() else a
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def safe_equal(a: Any, b: Any) -> bool:
|
|
135
|
+
"""安全相等比较:Decimal NaN 参与比较会抛 InvalidOperation,视为不相等。"""
|
|
136
|
+
try:
|
|
137
|
+
return bool(a == b)
|
|
138
|
+
except InvalidOperation:
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def std_equal(a: StdValue | None, b: StdValue | None) -> bool:
|
|
143
|
+
"""StdValue 结构相等(直接工作在 Std 节点上,不经 Python 降维)。
|
|
144
|
+
|
|
145
|
+
语义:
|
|
146
|
+
- 字面量:kind 相同且值相等;int/float 数值交叉相等(eq(42) 对 float 42 成立)
|
|
147
|
+
- bool 与 int 不相等(避免 Python ``True == 1`` 陷阱)
|
|
148
|
+
- NaN 与任何值不相等(IEEE 754)
|
|
149
|
+
- 数组:等长且逐元素相等
|
|
150
|
+
- 对象:等字段集且逐字段值相等(与字段顺序无关)
|
|
151
|
+
"""
|
|
152
|
+
if a is None or b is None:
|
|
153
|
+
return a is b
|
|
154
|
+
if isinstance(a, StdLiteral) and isinstance(b, StdLiteral):
|
|
155
|
+
if a.kind == b.kind:
|
|
156
|
+
return safe_equal(a.value, b.value)
|
|
157
|
+
if {a.kind, b.kind} == {'int', 'float'}:
|
|
158
|
+
na = as_number(a)
|
|
159
|
+
nb = as_number(b)
|
|
160
|
+
if na is not None and nb is not None:
|
|
161
|
+
return safe_equal(na, nb)
|
|
162
|
+
return False
|
|
163
|
+
if isinstance(a, StdArray) and isinstance(b, StdArray):
|
|
164
|
+
if len(a.elements) != len(b.elements):
|
|
165
|
+
return False
|
|
166
|
+
return all(std_equal(x, y) for x, y in zip(a.elements, b.elements))
|
|
167
|
+
if isinstance(a, StdObject) and isinstance(b, StdObject):
|
|
168
|
+
if len(a.fields) != len(b.fields):
|
|
169
|
+
return False
|
|
170
|
+
for f in a.fields:
|
|
171
|
+
other = b.get(f.name)
|
|
172
|
+
if other is None or not std_equal(f.value, other.value):
|
|
173
|
+
return False
|
|
174
|
+
return True
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def as_std_value(v: Any) -> StdValue:
|
|
179
|
+
"""约束参数(Python 值)→ StdValue(与值同构后比较)。"""
|
|
180
|
+
if v is None:
|
|
181
|
+
return StdLiteral(kind='null', value=None)
|
|
182
|
+
if isinstance(v, bool):
|
|
183
|
+
return StdLiteral(kind='bool', value=v)
|
|
184
|
+
if isinstance(v, int):
|
|
185
|
+
return StdLiteral(kind='int', value=v)
|
|
186
|
+
if isinstance(v, Decimal):
|
|
187
|
+
return StdLiteral(kind='float', value=v)
|
|
188
|
+
if isinstance(v, str):
|
|
189
|
+
return StdLiteral(kind='str', value=v)
|
|
190
|
+
if isinstance(v, (list, tuple)):
|
|
191
|
+
items = cast(list[Any], v)
|
|
192
|
+
return StdArray(elements=[as_std_value(e) for e in items])
|
|
193
|
+
if isinstance(v, dict):
|
|
194
|
+
mapping = cast(dict[Any, Any], v)
|
|
195
|
+
return StdObject(fields=[StdField(name=str(k), value=as_std_value(e)) for k, e in mapping.items()])
|
|
196
|
+
return StdLiteral(kind='str', value=str(v))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def as_spec(arg: Any) -> ResolvedConstraint | None:
|
|
200
|
+
"""嵌套参数 → 约束规格(ResolvedConstraint 或裸名字符串)。"""
|
|
201
|
+
if isinstance(arg, ResolvedConstraint):
|
|
202
|
+
return arg
|
|
203
|
+
if isinstance(arg, str):
|
|
204
|
+
return ResolvedConstraint(name=arg) # pyright: ignore[reportCallIssue]
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def spec_name(arg: Any) -> str:
|
|
209
|
+
"""取参数的约束名(has/field 的第一个参数是裸名字)。"""
|
|
210
|
+
if isinstance(arg, ResolvedConstraint):
|
|
211
|
+
return arg.name
|
|
212
|
+
return str(arg)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""内置字典约束:has / field。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from infinity_data.semantic.builder.models import StdObject
|
|
8
|
+
from infinity_data.semantic.registry._core import (
|
|
9
|
+
ConstraintResult,
|
|
10
|
+
Executor,
|
|
11
|
+
fail_result,
|
|
12
|
+
ok_result,
|
|
13
|
+
)
|
|
14
|
+
from infinity_data.semantic.registry._core import (
|
|
15
|
+
as_spec as _as_spec,
|
|
16
|
+
)
|
|
17
|
+
from infinity_data.semantic.registry._core import (
|
|
18
|
+
spec_name as _spec_name,
|
|
19
|
+
)
|
|
20
|
+
from infinity_data.tokenizer.models.raw_tokens import SourceRange
|
|
21
|
+
|
|
22
|
+
__all__ = ['_check_has', '_check_field']
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _check_has(
|
|
26
|
+
val: Any,
|
|
27
|
+
source: SourceRange | None,
|
|
28
|
+
path: str,
|
|
29
|
+
args: list[Any],
|
|
30
|
+
executor: Executor,
|
|
31
|
+
) -> ConstraintResult:
|
|
32
|
+
if not isinstance(val, StdObject):
|
|
33
|
+
return fail_result('constraint.has_only', {}, source, path)
|
|
34
|
+
key = _spec_name(args[0])
|
|
35
|
+
if val.get(key) is not None:
|
|
36
|
+
return ok_result()
|
|
37
|
+
return fail_result('constraint.has_missing', {'key': key}, source, path)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _check_field(
|
|
41
|
+
val: Any,
|
|
42
|
+
source: SourceRange | None,
|
|
43
|
+
path: str,
|
|
44
|
+
args: list[Any],
|
|
45
|
+
executor: Executor,
|
|
46
|
+
) -> ConstraintResult:
|
|
47
|
+
if not isinstance(val, StdObject):
|
|
48
|
+
return fail_result('constraint.field_only', {}, source, path)
|
|
49
|
+
name = _spec_name(args[0])
|
|
50
|
+
spec = _as_spec(args[1])
|
|
51
|
+
if spec is None:
|
|
52
|
+
return fail_result('constraint.field_need', {}, source, path)
|
|
53
|
+
f = val.get(name)
|
|
54
|
+
if f is None:
|
|
55
|
+
return fail_result('constraint.field_missing', {'field': name}, source, path)
|
|
56
|
+
return executor(spec, f.value, source, f'{path}.{name}')
|