infinity-data 2.0.0__tar.gz → 2.1.0__tar.gz
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-2.0.0/src/infinity_data.egg-info → infinity_data-2.1.0}/PKG-INFO +1 -1
- {infinity_data-2.0.0 → infinity_data-2.1.0}/pyproject.toml +1 -1
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/__init__.py +2 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/models.py +20 -6
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/parser.py +43 -15
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/builder/builder.py +16 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/constraints.py +1 -1
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/diagnostics.py +4 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/__init__.py +8 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/general.py +28 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/imports.py +12 -4
- {infinity_data-2.0.0 → infinity_data-2.1.0/src/infinity_data.egg-info}/PKG-INFO +1 -1
- {infinity_data-2.0.0 → infinity_data-2.1.0}/LICENSE +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/README.md +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/setup.cfg +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/emit/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/emit/converter.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/frontend.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/diagnostics.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/file.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/ll1_stream.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/location.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/infra/path.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/diagnostics.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/parser/token_stream.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/pipeline.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/py.typed +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/config.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/errors.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/mediator.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/sandbox/schema.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/builder/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/builder/models.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/executor/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/executor/executor.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/jsonpath.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/_core.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/dict_constraints.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/logic.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/types.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/identity.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/models.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/resolver/resolver.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/std.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/char_stream.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/diagnostics.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/finalizer.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/models/__init__.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/models/raw_tokens.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/models/tokens.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/tokenizer.py +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data.egg-info/SOURCES.txt +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data.egg-info/dependency_links.txt +0 -0
- {infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data.egg-info/top_level.txt +0 -0
|
@@ -22,6 +22,7 @@ from infinity_data.parser.models import (
|
|
|
22
22
|
DictValue,
|
|
23
23
|
Document,
|
|
24
24
|
DollarValue,
|
|
25
|
+
EnvImportItem,
|
|
25
26
|
EnvImportStmt,
|
|
26
27
|
ErrorConstraint,
|
|
27
28
|
ErrorStatement,
|
|
@@ -59,6 +60,7 @@ __all__ = [
|
|
|
59
60
|
'DictValue',
|
|
60
61
|
'Document',
|
|
61
62
|
'DollarValue',
|
|
63
|
+
'EnvImportItem',
|
|
62
64
|
'EnvImportStmt',
|
|
63
65
|
'ErrorConstraint',
|
|
64
66
|
'ErrorStatement',
|
|
@@ -120,16 +120,30 @@ class TemplateImportStmt(AstNode):
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
@dataclass
|
|
123
|
-
class
|
|
124
|
-
"""
|
|
123
|
+
class EnvImportItem(AstNode):
|
|
124
|
+
"""环境变量导入项: NAME [as NEW_NAME]。"""
|
|
125
125
|
|
|
126
126
|
name: str # 环境变量名
|
|
127
|
-
alias: str | None # 别名(可选)
|
|
127
|
+
alias: str | None = None # 别名(可选)
|
|
128
128
|
|
|
129
129
|
def canonical(self) -> str:
|
|
130
130
|
if self.alias is None:
|
|
131
|
-
return
|
|
132
|
-
return f'
|
|
131
|
+
return self.name
|
|
132
|
+
return f'{self.name} as {self.alias}'
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class EnvImportStmt(AstNode):
|
|
137
|
+
"""环境变量导入: !env import NAME1 [as NEW1], NAME2 [as NEW2], ..."""
|
|
138
|
+
|
|
139
|
+
items: list[EnvImportItem] # 导入项列表(项之间必须用逗号分隔)
|
|
140
|
+
|
|
141
|
+
def children(self) -> Iterable[AstNode]:
|
|
142
|
+
return self.items
|
|
143
|
+
|
|
144
|
+
def canonical(self) -> str:
|
|
145
|
+
items = ', '.join(i.canonical() for i in self.items)
|
|
146
|
+
return f'!env import {items}'
|
|
133
147
|
|
|
134
148
|
|
|
135
149
|
@dataclass
|
|
@@ -178,7 +192,7 @@ class FileImportStmt(AstNode):
|
|
|
178
192
|
"""配置文件导入: !file "path" as <format> import .path.to.key as alias, ..."""
|
|
179
193
|
|
|
180
194
|
file_path: str # 文件路径
|
|
181
|
-
format: str | None # 文件格式: "yaml", "json", "toml" 或 None(自动检测后缀)
|
|
195
|
+
format: str | None # 文件格式: "yaml", "json", "toml", "raw" 或 None(自动检测后缀)
|
|
182
196
|
imports: list[FileImportItem]
|
|
183
197
|
|
|
184
198
|
def children(self) -> Iterable[AstNode]:
|
|
@@ -17,6 +17,7 @@ from infinity_data.parser.models import (
|
|
|
17
17
|
DictValue,
|
|
18
18
|
Document,
|
|
19
19
|
DollarValue,
|
|
20
|
+
EnvImportItem,
|
|
20
21
|
EnvImportStmt,
|
|
21
22
|
ErrorConstraint,
|
|
22
23
|
ErrorStatement,
|
|
@@ -272,18 +273,29 @@ class Parser:
|
|
|
272
273
|
|
|
273
274
|
@staticmethod
|
|
274
275
|
def _parse_env_import(stream: TokenStream, collector: DiagnosticCollector, kw_tok: EnvImportToken) -> EnvImportStmt:
|
|
275
|
-
"""!env import
|
|
276
|
+
"""!env import NAME1 [as NEW1], NAME2 [as NEW2], ..."""
|
|
276
277
|
Parser._expect_keyword(stream, collector, 'import')
|
|
277
|
-
name_tok = stream.expect(IdentifierToken)
|
|
278
278
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
279
|
+
# 导入项列表(项之间必须用逗号分隔)
|
|
280
|
+
items: list[EnvImportItem] = []
|
|
281
|
+
items.append(Parser._parse_env_import_item(stream, collector))
|
|
282
|
+
|
|
283
|
+
while True:
|
|
284
|
+
tok = stream.peek()
|
|
285
|
+
if isinstance(tok, CommaToken):
|
|
286
|
+
stream.advance()
|
|
287
|
+
items.append(Parser._parse_env_import_item(stream, collector))
|
|
288
|
+
elif isinstance(tok, IdentifierToken):
|
|
289
|
+
# 同一行内空格分隔(无逗号/换行)→ 报缺失逗号,容错继续(漏逗号)
|
|
290
|
+
collector.add(diag('parse.import_missing_comma', {}, tok.raw.source))
|
|
291
|
+
items.append(Parser._parse_env_import_item(stream, collector))
|
|
292
|
+
else:
|
|
293
|
+
# 换行/EOF 等 → 导入列表结束(换行后是新的顶层语句,不是导入项)
|
|
294
|
+
break
|
|
283
295
|
|
|
284
|
-
# 导入语句必须换行/EOF
|
|
285
|
-
#
|
|
286
|
-
#
|
|
296
|
+
# 导入语句必须换行/EOF 结尾:若不检查,同一行的逗号会被顶层 skip_separators
|
|
297
|
+
# 吞掉(`!env import A as a, x = 1` 被误认为合法),与 !from / !file 的
|
|
298
|
+
# 「尾部必须换行」行为不一致。
|
|
287
299
|
tok = stream.peek()
|
|
288
300
|
# 显式排除 NoNextType 以收窄类型:stream.eof() 是方法调用,无法据此收窄 tok,
|
|
289
301
|
# 否则 tok 仍为 Token | NoNextType,访问 tok.raw 会触发类型错误。
|
|
@@ -300,6 +312,22 @@ class Parser:
|
|
|
300
312
|
# 否则语句 source 会把下一行行首吞入(与 Field 不一致)
|
|
301
313
|
return EnvImportStmt(
|
|
302
314
|
source=stream.span_from(kw_tok),
|
|
315
|
+
items=items,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
@staticmethod
|
|
319
|
+
def _parse_env_import_item(stream: TokenStream, collector: DiagnosticCollector) -> EnvImportItem:
|
|
320
|
+
"""解析单个导入项: NAME [as NEW_NAME]。"""
|
|
321
|
+
first = stream.peek()
|
|
322
|
+
name_tok = stream.expect(IdentifierToken)
|
|
323
|
+
|
|
324
|
+
alias = None
|
|
325
|
+
if Parser._peek_keyword(stream, 'as'):
|
|
326
|
+
stream.advance()
|
|
327
|
+
alias = stream.expect(IdentifierToken).name
|
|
328
|
+
|
|
329
|
+
return EnvImportItem(
|
|
330
|
+
source=stream.span_from(first),
|
|
303
331
|
name=name_tok.name,
|
|
304
332
|
alias=alias,
|
|
305
333
|
)
|
|
@@ -890,7 +918,7 @@ class Parser:
|
|
|
890
918
|
|
|
891
919
|
支持:
|
|
892
920
|
- int, str, bool, float, list, dict, ?, object
|
|
893
|
-
- type? →
|
|
921
|
+
- type? → any(type, ?)
|
|
894
922
|
- <constraint, constraint, ...> → all(constraint, ...)
|
|
895
923
|
- <any(...)>, <one(...)>, <not(...)>, <all(...)>
|
|
896
924
|
"""
|
|
@@ -929,7 +957,7 @@ class Parser:
|
|
|
929
957
|
|
|
930
958
|
if isinstance(stream.peek(), QuestionToken):
|
|
931
959
|
stream.advance()
|
|
932
|
-
# 直接展开: type? →
|
|
960
|
+
# 直接展开: type? → any(type, ?)
|
|
933
961
|
return Constraints(
|
|
934
962
|
source=stream.span_from(first),
|
|
935
963
|
constraints=[Parser._nullable(ident)],
|
|
@@ -938,7 +966,7 @@ class Parser:
|
|
|
938
966
|
# 单约束函数调用可省略尖括号: field: regex("re") = ...
|
|
939
967
|
if isinstance(stream.peek(), LparenToken):
|
|
940
968
|
call = Parser._parse_constraint_call(stream, collector, first)
|
|
941
|
-
# 调用后也可空: regex("re")? →
|
|
969
|
+
# 调用后也可空: regex("re")? → any(regex("re"), ?)
|
|
942
970
|
if isinstance(stream.peek(), QuestionToken):
|
|
943
971
|
stream.advance()
|
|
944
972
|
call = Parser._nullable(call)
|
|
@@ -971,10 +999,10 @@ class Parser:
|
|
|
971
999
|
|
|
972
1000
|
@staticmethod
|
|
973
1001
|
def _nullable(c: Constraint) -> ConstraintCall:
|
|
974
|
-
"""可空包装:constraint? →
|
|
1002
|
+
"""可空包装:constraint? → any(constraint, ?)。"""
|
|
975
1003
|
return ConstraintCall(
|
|
976
1004
|
source=c.source,
|
|
977
|
-
name='
|
|
1005
|
+
name='any',
|
|
978
1006
|
arguments=[c, ConstraintIdent(source=c.source, name='?')],
|
|
979
1007
|
)
|
|
980
1008
|
|
|
@@ -1025,7 +1053,7 @@ class Parser:
|
|
|
1025
1053
|
message=f'无法解析的约束: {bad_tok.raw.type.name}',
|
|
1026
1054
|
)
|
|
1027
1055
|
|
|
1028
|
-
# 可空后缀: constraint? →
|
|
1056
|
+
# 可空后缀: constraint? → any(constraint, ?)
|
|
1029
1057
|
if isinstance(stream.peek(), QuestionToken):
|
|
1030
1058
|
stream.advance()
|
|
1031
1059
|
return Parser._nullable(base)
|
|
@@ -522,6 +522,11 @@ class AstBuilder:
|
|
|
522
522
|
return raw
|
|
523
523
|
if isinstance(raw, (StdArray, StdObject)):
|
|
524
524
|
return raw # 结构化值 cast 不适用
|
|
525
|
+
# 空字面量:as 转换**保持传播**(null → null、noexist → noexist),
|
|
526
|
+
# 不做任何转换、不产生警告——三态可空语义不因显式 as 而丢失
|
|
527
|
+
# (此时 raw 已收窄为 StdLiteral)
|
|
528
|
+
if raw.kind in ('null', 'noexist'):
|
|
529
|
+
return raw
|
|
525
530
|
raw = raw.value # StdLiteral:提取 Python 值走转换
|
|
526
531
|
|
|
527
532
|
match type_cast:
|
|
@@ -588,6 +593,17 @@ class AstBuilder:
|
|
|
588
593
|
)
|
|
589
594
|
return StdLiteral(kind='float', value=decimal.Decimal(0))
|
|
590
595
|
case 'str':
|
|
596
|
+
# as str = 字符串化:任意字面量按**语言字面量风格**转为字符串,
|
|
597
|
+
# 保证可 round-trip 还原回原值(bool → true/false、float 特殊值
|
|
598
|
+
# → nan/+inf/-inf),不泄漏 Python 内部表示;null/noexist 已在上方
|
|
599
|
+
# 保持传播,不进入本分支
|
|
600
|
+
if isinstance(raw, bool):
|
|
601
|
+
return StdLiteral(kind='str', value='true' if raw else 'false')
|
|
602
|
+
if isinstance(raw, decimal.Decimal):
|
|
603
|
+
if raw.is_nan():
|
|
604
|
+
return StdLiteral(kind='str', value='nan')
|
|
605
|
+
if raw.is_infinite():
|
|
606
|
+
return StdLiteral(kind='str', value='+inf' if raw > 0 else '-inf')
|
|
591
607
|
return StdLiteral(kind='str', value=str(raw))
|
|
592
608
|
case _:
|
|
593
609
|
return StdLiteral(kind='str', value=str(raw))
|
|
@@ -38,7 +38,7 @@ _INVALID_CONSTRAINT = '@invalid'
|
|
|
38
38
|
def expand_annotation(annotation: Constraints) -> Constraints:
|
|
39
39
|
"""展开约束语法糖:多约束 ``<a, b, c>`` → ``all(a, b, c)``。
|
|
40
40
|
|
|
41
|
-
(``type?`` → ``
|
|
41
|
+
(``type?`` → ``any(type, ?)`` 已在 parser 阶段展开。)
|
|
42
42
|
"""
|
|
43
43
|
if len(annotation.constraints) > 1:
|
|
44
44
|
return Constraints(
|
|
@@ -171,6 +171,10 @@ register_diagnostic_define(
|
|
|
171
171
|
diagnostic_define('constraint.size_out', '{path_prefix}大小 {size} 不在范围 [{lo}, {hi}] 内'),
|
|
172
172
|
diagnostic_define('constraint.each_need', '{path_prefix}each() 需要一个约束参数'),
|
|
173
173
|
diagnostic_define('constraint.each_only', '{path_prefix}each 约束适用于 list/dict,实际 {actual}'),
|
|
174
|
+
diagnostic_define('constraint.position_only', '{path_prefix}position 约束只适用于 list,实际 {actual}'),
|
|
175
|
+
diagnostic_define('constraint.position_index', '{path_prefix}position 下标必须是整数: {value!r}'),
|
|
176
|
+
diagnostic_define('constraint.position_need', '{path_prefix}position() 需要一个约束参数'),
|
|
177
|
+
diagnostic_define('constraint.position_out', '{path_prefix}下标 {index} 越界(list 大小 {size})'),
|
|
174
178
|
diagnostic_define('constraint.in_not_in', '{path_prefix}值 {value} 不在允许的值 {choices!r} 中'),
|
|
175
179
|
diagnostic_define('constraint.string_only', '{path_prefix}{constraint} 约束只适用于字符串'),
|
|
176
180
|
diagnostic_define('constraint.invalid_value', '{path_prefix}无效的{what} {value!r}'),
|
|
@@ -33,6 +33,7 @@ from infinity_data.semantic.registry.general import (
|
|
|
33
33
|
_check_ip6,
|
|
34
34
|
_check_negative,
|
|
35
35
|
_check_nonnegative,
|
|
36
|
+
_check_position,
|
|
36
37
|
_check_positive,
|
|
37
38
|
_check_range,
|
|
38
39
|
_check_regex,
|
|
@@ -139,6 +140,13 @@ class ConstraintRegistry:
|
|
|
139
140
|
self.register('range', _check_range, min_args=1, max_args=2, description='数值范围 range(ge[, le]),可省略一端')
|
|
140
141
|
self.register('size', _check_size, min_args=1, max_args=2, description='集合大小或字符串长度 size(ge[, le])')
|
|
141
142
|
self.register('each', _check_each, min_args=1, max_args=1, description='每个元素均满足 each(constraint)')
|
|
143
|
+
self.register(
|
|
144
|
+
'position',
|
|
145
|
+
_check_position,
|
|
146
|
+
min_args=2,
|
|
147
|
+
max_args=2,
|
|
148
|
+
description='指定下标元素约束 position(index, constraint)',
|
|
149
|
+
)
|
|
142
150
|
self.register('in', _check_in, min_args=1, description='值在给定选项中 in(choice, ...)')
|
|
143
151
|
self.register('ip', _check_ip, description='IPv4 或 IPv6 地址')
|
|
144
152
|
self.register('ip4', _check_ip4, description='IPv4 地址')
|
|
@@ -41,6 +41,7 @@ __all__ = [
|
|
|
41
41
|
'_check_range',
|
|
42
42
|
'_check_size',
|
|
43
43
|
'_check_each',
|
|
44
|
+
'_check_position',
|
|
44
45
|
'_check_in',
|
|
45
46
|
'_check_ip',
|
|
46
47
|
'_check_ip4',
|
|
@@ -137,6 +138,33 @@ def _check_each(
|
|
|
137
138
|
return ok_result()
|
|
138
139
|
|
|
139
140
|
|
|
141
|
+
def _check_position(
|
|
142
|
+
val: StdValue | None,
|
|
143
|
+
source: SourceRange | None,
|
|
144
|
+
path: str,
|
|
145
|
+
args: list[Any],
|
|
146
|
+
executor: Executor,
|
|
147
|
+
) -> ConstraintResult:
|
|
148
|
+
"""position(index, constraint):list 指定下标(0-based)元素满足约束。
|
|
149
|
+
|
|
150
|
+
- 值不是 list → 不满足
|
|
151
|
+
- 下标参数非整数(含 bool)→ 不满足(参数错误)
|
|
152
|
+
- 下标越界(< 0 或 >= size,元素不存在)→ 不满足
|
|
153
|
+
- 否则递归执行 constraint 于该元素
|
|
154
|
+
"""
|
|
155
|
+
if not isinstance(val, StdArray):
|
|
156
|
+
return fail_result('constraint.position_only', {'actual': describe(val)}, source, path)
|
|
157
|
+
idx = args[0]
|
|
158
|
+
if isinstance(idx, bool) or not isinstance(idx, int):
|
|
159
|
+
return fail_result('constraint.position_index', {'value': idx}, source, path)
|
|
160
|
+
spec = _as_spec(args[1])
|
|
161
|
+
if spec is None:
|
|
162
|
+
return fail_result('constraint.position_need', {}, source, path)
|
|
163
|
+
if idx < 0 or idx >= len(val.elements):
|
|
164
|
+
return fail_result('constraint.position_out', {'index': idx, 'size': len(val.elements)}, source, path)
|
|
165
|
+
return executor(spec, val.elements[idx], source, f'{path}[{idx}]')
|
|
166
|
+
|
|
167
|
+
|
|
140
168
|
def _check_in(
|
|
141
169
|
val: StdValue | None,
|
|
142
170
|
source: SourceRange | None,
|
|
@@ -36,6 +36,10 @@ _FORMAT_MAP: dict[str, str] = {
|
|
|
36
36
|
'.yaml': 'yaml',
|
|
37
37
|
'.yml': 'yaml',
|
|
38
38
|
'.toml': 'toml',
|
|
39
|
+
'.txt': 'raw',
|
|
40
|
+
'.text': 'raw',
|
|
41
|
+
'.md': 'raw',
|
|
42
|
+
'.log': 'raw',
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
|
|
@@ -93,14 +97,15 @@ class ImportResolver:
|
|
|
93
97
|
namespace: dict[str, StdValue],
|
|
94
98
|
collector: DiagnosticCollector,
|
|
95
99
|
) -> None:
|
|
96
|
-
"""!env import
|
|
100
|
+
"""!env import NAME1 [as NEW1], NAME2 [as NEW2], ...
|
|
97
101
|
|
|
98
102
|
未授权环境变量**总是失败**(无论 strict):Sandbox.getenv 直接抛
|
|
99
103
|
:class:`SandboxError`,不会退化为空字符串注入。
|
|
100
104
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
for item in stmt.items:
|
|
106
|
+
name = item.alias or item.name
|
|
107
|
+
raw = self._sandbox.getenv(item.name, source=item.source)
|
|
108
|
+
self._bind(namespace, name, python_to_std(raw), collector, item.source)
|
|
104
109
|
|
|
105
110
|
def _resolve_file(
|
|
106
111
|
self,
|
|
@@ -162,6 +167,9 @@ class ImportResolver:
|
|
|
162
167
|
) -> Any | None:
|
|
163
168
|
"""按格式解析数据内容(文本 loads)。"""
|
|
164
169
|
try:
|
|
170
|
+
if fmt == 'raw':
|
|
171
|
+
# raw:直接导入字符串(不做任何解析,保留文件原文)
|
|
172
|
+
return text
|
|
165
173
|
if fmt == 'json':
|
|
166
174
|
return json.loads(text)
|
|
167
175
|
if fmt in ('yaml', 'yml'):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/semantic/registry/dict_constraints.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{infinity_data-2.0.0 → infinity_data-2.1.0}/src/infinity_data/tokenizer/models/raw_tokens.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|