langroid 0.16.7__py3-none-any.whl → 0.17.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.
- langroid/agent/base.py +45 -21
- langroid/agent/chat_agent.py +22 -14
- langroid/agent/chat_document.py +22 -13
- langroid/agent/tool_message.py +11 -11
- langroid/agent/tools/file_tools.py +234 -0
- langroid/agent/xml_tool_message.py +178 -45
- langroid/utils/constants.py +2 -0
- langroid/utils/git_utils.py +251 -0
- langroid/utils/system.py +78 -0
- {langroid-0.16.7.dist-info → langroid-0.17.0.dist-info}/METADATA +6 -3
- {langroid-0.16.7.dist-info → langroid-0.17.0.dist-info}/RECORD +14 -89
- pyproject.toml +3 -2
- langroid/agent/md_tool_message_grammar.py +0 -455
- langroid/agent/tools/code_file_tool_parse.py +0 -150
- langroid/agent/tools/code_file_tool_pyparsing.py +0 -194
- langroid/agent/tools/code_file_tool_pyparsing2.py +0 -199
- langroid/agent/tools/extract_tool.py +0 -96
- langroid/agent/tools/formatted_model_custom.py +0 -150
- langroid/agent/tools/formatted_model_custom2.py +0 -168
- langroid/agent/tools/formatted_model_custom3.py +0 -279
- langroid/agent/tools/formatted_model_custom4.py +0 -395
- langroid/agent/tools/formatted_model_jinja.py +0 -133
- langroid/agent/tools/formatted_model_jinja.py-e +0 -122
- langroid/agent/tools/formatted_model_jinja2.py +0 -145
- langroid/agent/tools/formatted_model_jinja2.py-e +0 -135
- langroid/agent/tools/formatted_model_lark.py +0 -0
- langroid/agent/tools/formatted_model_lark2.py +0 -168
- langroid/agent/tools/formatted_model_parse.py +0 -105
- langroid/agent/tools/formatted_model_parse.py-e +0 -98
- langroid/agent/tools/formatted_model_parse2.py +0 -113
- langroid/agent/tools/formatted_model_parse2.py-e +0 -109
- langroid/agent/tools/formatted_model_parse3.py +0 -114
- langroid/agent/tools/formatted_model_parse3.py-e +0 -110
- langroid/agent/tools/formatted_model_parsimon.py +0 -194
- langroid/agent/tools/formatted_model_parsimon.py-e +0 -186
- langroid/agent/tools/formatted_model_pyparsing.py +0 -169
- langroid/agent/tools/formatted_model_pyparsing.py-e +0 -149
- langroid/agent/tools/formatted_model_pyparsing2.py +0 -159
- langroid/agent/tools/formatted_model_pyparsing2.py-e +0 -143
- langroid/agent/tools/formatted_model_pyparsing3.py +0 -133
- langroid/agent/tools/formatted_model_pyparsing3.py-e +0 -121
- langroid/agent/tools/formatted_model_pyparsing4.py +0 -213
- langroid/agent/tools/formatted_model_pyparsing4.py-e +0 -176
- langroid/agent/tools/formatted_model_pyparsing5.py +0 -173
- langroid/agent/tools/formatted_model_pyparsing5.py-e +0 -142
- langroid/agent/tools/formatted_model_regex.py +0 -246
- langroid/agent/tools/formatted_model_regex.py-e +0 -248
- langroid/agent/tools/formatted_model_regex2.py +0 -250
- langroid/agent/tools/formatted_model_regex2.py-e +0 -253
- langroid/agent/tools/formatted_model_tatsu.py +0 -172
- langroid/agent/tools/formatted_model_tatsu.py-e +0 -160
- langroid/agent/tools/formatted_model_template.py +0 -217
- langroid/agent/tools/formatted_model_template.py-e +0 -200
- langroid/agent/tools/formatted_model_xml.py +0 -178
- langroid/agent/tools/formatted_model_xml2.py +0 -178
- langroid/agent/tools/formatted_model_xml3.py +0 -132
- langroid/agent/tools/formatted_model_xml4.py +0 -130
- langroid/agent/tools/formatted_model_xml5.py +0 -130
- langroid/agent/tools/formatted_model_xml6.py +0 -113
- langroid/agent/tools/formatted_model_xml7.py +0 -117
- langroid/agent/tools/formatted_model_xml8.py +0 -164
- langroid/agent/tools/generator_tool.py +0 -20
- langroid/agent/tools/generic_tool.py +0 -165
- langroid/agent/tools/generic_tool_tatsu.py +0 -275
- langroid/agent/tools/grammar_based_model.py +0 -132
- langroid/agent/tools/grammar_based_model.py-e +0 -128
- langroid/agent/tools/grammar_based_model_lark.py +0 -156
- langroid/agent/tools/grammar_based_model_lark.py-e +0 -153
- langroid/agent/tools/grammar_based_model_parse.py +0 -86
- langroid/agent/tools/grammar_based_model_parse.py-e +0 -80
- langroid/agent/tools/grammar_based_model_parsimonious.py +0 -129
- langroid/agent/tools/grammar_based_model_parsimonious.py-e +0 -120
- langroid/agent/tools/grammar_based_model_pyparsing.py +0 -105
- langroid/agent/tools/grammar_based_model_pyparsing.py-e +0 -103
- langroid/agent/tools/grammar_based_model_regex.py +0 -139
- langroid/agent/tools/grammar_based_model_regex.py-e +0 -130
- langroid/agent/tools/grammar_based_model_regex2.py +0 -124
- langroid/agent/tools/grammar_based_model_regex2.py-e +0 -116
- langroid/agent/tools/grammar_based_model_tatsu.py +0 -80
- langroid/agent/tools/grammar_based_model_tatsu.py-e +0 -77
- langroid/agent/tools/lark_earley_example.py +0 -135
- langroid/agent/tools/lark_earley_example.py-e +0 -117
- langroid/agent/tools/lark_example.py +0 -72
- langroid/agent/tools/note_tool.py +0 -0
- langroid/agent/tools/parse_example.py +0 -76
- langroid/agent/tools/parse_example2.py +0 -87
- langroid/agent/tools/parse_example3.py +0 -42
- langroid/agent/tools/parse_test.py +0 -791
- langroid/agent/tools/run_python_code.py +0 -60
- {langroid-0.16.7.dist-info → langroid-0.17.0.dist-info}/LICENSE +0 -0
- {langroid-0.16.7.dist-info → langroid-0.17.0.dist-info}/WHEEL +0 -0
@@ -1,124 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
from abc import ABC, abstractmethod
|
3
|
-
|
4
|
-
from langroid.pydantic_v1 import BaseModel
|
5
|
-
|
6
|
-
|
7
|
-
class FormattingModel(BaseModel, ABC):
|
8
|
-
@classmethod
|
9
|
-
@abstractmethod
|
10
|
-
def format_spec(cls) -> str:
|
11
|
-
pass
|
12
|
-
|
13
|
-
@classmethod
|
14
|
-
@abstractmethod
|
15
|
-
def start_token(cls) -> str:
|
16
|
-
pass
|
17
|
-
|
18
|
-
@classmethod
|
19
|
-
@abstractmethod
|
20
|
-
def end_token(cls) -> str:
|
21
|
-
pass
|
22
|
-
|
23
|
-
@classmethod
|
24
|
-
def _create_regex_pattern(cls) -> str:
|
25
|
-
spec = cls.format_spec()
|
26
|
-
# Replace {field_name} with (?P<field_name>.*?)
|
27
|
-
pattern = re.sub(r"\{(\w+)\}", lambda m: f"(?P<{m.group(1)}>.*?)", spec)
|
28
|
-
# Replace newlines with \s* to allow flexible whitespace
|
29
|
-
pattern = pattern.replace("\n", r"\s*")
|
30
|
-
return f"{re.escape(cls.start_token())}\\s*{pattern}\\s*{re.escape(cls.end_token())}"
|
31
|
-
|
32
|
-
@classmethod
|
33
|
-
def parse(cls, text: str) -> "FormattingModel":
|
34
|
-
pattern = cls._create_regex_pattern()
|
35
|
-
match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
|
36
|
-
if match:
|
37
|
-
return cls(**{k: v.strip() for k, v in match.groupdict().items()})
|
38
|
-
raise ValueError(
|
39
|
-
f"Text does not match the expected format. Pattern: {pattern}, Text: {text}"
|
40
|
-
)
|
41
|
-
|
42
|
-
def generate(self) -> str:
|
43
|
-
content = self.format_spec().format(**self.dict())
|
44
|
-
return f"{self.start_token()}\n{content}\n{self.end_token()}"
|
45
|
-
|
46
|
-
|
47
|
-
class PersonModel(FormattingModel):
|
48
|
-
name: str
|
49
|
-
age: int
|
50
|
-
city: str
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def format_spec(cls) -> str:
|
54
|
-
return "name: {name}\nage is {age}\nlives in {city}"
|
55
|
-
|
56
|
-
@classmethod
|
57
|
-
def start_token(cls) -> str:
|
58
|
-
return "<spec>"
|
59
|
-
|
60
|
-
@classmethod
|
61
|
-
def end_token(cls) -> str:
|
62
|
-
return "</spec>"
|
63
|
-
|
64
|
-
|
65
|
-
def test_round_trip(model_class, input_string):
|
66
|
-
# Parse the input string
|
67
|
-
parsed_model = model_class.parse(input_string)
|
68
|
-
print(f"Parsed model: {parsed_model}")
|
69
|
-
|
70
|
-
# Generate a string from the parsed model
|
71
|
-
generated_string = parsed_model.generate()
|
72
|
-
print(f"Generated string:\n{generated_string}")
|
73
|
-
|
74
|
-
# Parse the generated string
|
75
|
-
reparsed_model = model_class.parse(generated_string)
|
76
|
-
print(f"Reparsed model: {reparsed_model}")
|
77
|
-
|
78
|
-
# Assert that the original parsed model and the reparsed model are equal
|
79
|
-
assert (
|
80
|
-
parsed_model == reparsed_model
|
81
|
-
), "Round trip failed: original and reparsed models are not equal"
|
82
|
-
|
83
|
-
# Assert that all fields are present and have the correct types
|
84
|
-
for field, field_type in model_class.__annotations__.items():
|
85
|
-
assert hasattr(parsed_model, field), f"Field {field} is missing"
|
86
|
-
assert isinstance(
|
87
|
-
getattr(parsed_model, field), field_type
|
88
|
-
), f"Field {field} has incorrect type"
|
89
|
-
|
90
|
-
print("Round trip test passed successfully!")
|
91
|
-
|
92
|
-
|
93
|
-
if __name__ == "__main__":
|
94
|
-
# Test case 1: Standard formatting
|
95
|
-
test_string1 = """
|
96
|
-
<spec>
|
97
|
-
name: John Doe
|
98
|
-
age is 30
|
99
|
-
lives in New York
|
100
|
-
</spec>
|
101
|
-
"""
|
102
|
-
test_round_trip(PersonModel, test_string1)
|
103
|
-
|
104
|
-
print("\n" + "=" * 50 + "\n")
|
105
|
-
|
106
|
-
# Test case 2: Varying whitespace
|
107
|
-
test_string2 = "<spec>name: Alice \nage is 25 \nlives in Tokyo</spec>"
|
108
|
-
test_round_trip(PersonModel, test_string2)
|
109
|
-
|
110
|
-
print("\n" + "=" * 50 + "\n")
|
111
|
-
|
112
|
-
# Test case 3: Multiline values
|
113
|
-
test_string3 = """
|
114
|
-
<spec>
|
115
|
-
name: Bob
|
116
|
-
Smith
|
117
|
-
age is 40
|
118
|
-
lives in San
|
119
|
-
Francisco
|
120
|
-
</spec>
|
121
|
-
"""
|
122
|
-
test_round_trip(PersonModel, test_string3)
|
123
|
-
|
124
|
-
print("All tests passed successfully!")
|
@@ -1,116 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
from abc import ABC, abstractmethod
|
3
|
-
from typing import Dict, Any
|
4
|
-
from pydantic import BaseModel
|
5
|
-
|
6
|
-
class FormattingModel(BaseModel, ABC):
|
7
|
-
@classmethod
|
8
|
-
@abstractmethod
|
9
|
-
def format_spec(cls) -> str:
|
10
|
-
pass
|
11
|
-
|
12
|
-
@classmethod
|
13
|
-
@abstractmethod
|
14
|
-
def start_token(cls) -> str:
|
15
|
-
pass
|
16
|
-
|
17
|
-
@classmethod
|
18
|
-
@abstractmethod
|
19
|
-
def end_token(cls) -> str:
|
20
|
-
pass
|
21
|
-
|
22
|
-
@classmethod
|
23
|
-
def _create_regex_pattern(cls) -> str:
|
24
|
-
spec = cls.format_spec()
|
25
|
-
# Replace {field_name} with (?P<field_name>.*?)
|
26
|
-
pattern = re.sub(r'\{(\w+)\}', lambda m: f'(?P<{m.group(1)}>.*?)', spec)
|
27
|
-
# Replace newlines with \s* to allow flexible whitespace
|
28
|
-
pattern = pattern.replace('\n', r'\s*')
|
29
|
-
return f"{re.escape(cls.start_token())}\\s*{pattern}\\s*{re.escape(cls.end_token())}"
|
30
|
-
|
31
|
-
@classmethod
|
32
|
-
def parse(cls, text: str) -> 'FormattingModel':
|
33
|
-
pattern = cls._create_regex_pattern()
|
34
|
-
match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
|
35
|
-
if match:
|
36
|
-
return cls(**{k: v.strip() for k, v in match.groupdict().items()})
|
37
|
-
raise ValueError(f"Text does not match the expected format. Pattern: {pattern}, Text: {text}")
|
38
|
-
|
39
|
-
def generate(self) -> str:
|
40
|
-
content = self.format_spec().format(**self.dict())
|
41
|
-
return f"{self.start_token()}\n{content}\n{self.end_token()}"
|
42
|
-
|
43
|
-
class PersonModel(FormattingModel):
|
44
|
-
name: str
|
45
|
-
age: int
|
46
|
-
city: str
|
47
|
-
|
48
|
-
@classmethod
|
49
|
-
def format_spec(cls) -> str:
|
50
|
-
return "name: {name}\nage is {age}\nlives in {city}"
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def start_token(cls) -> str:
|
54
|
-
return "<spec>"
|
55
|
-
|
56
|
-
@classmethod
|
57
|
-
def end_token(cls) -> str:
|
58
|
-
return "</spec>"
|
59
|
-
|
60
|
-
def test_round_trip(model_class, input_string):
|
61
|
-
# Parse the input string
|
62
|
-
parsed_model = model_class.parse(input_string)
|
63
|
-
print(f"Parsed model: {parsed_model}")
|
64
|
-
|
65
|
-
# Generate a string from the parsed model
|
66
|
-
generated_string = parsed_model.generate()
|
67
|
-
print(f"Generated string:\n{generated_string}")
|
68
|
-
|
69
|
-
# Parse the generated string
|
70
|
-
reparsed_model = model_class.parse(generated_string)
|
71
|
-
print(f"Reparsed model: {reparsed_model}")
|
72
|
-
|
73
|
-
# Assert that the original parsed model and the reparsed model are equal
|
74
|
-
assert parsed_model == reparsed_model, "Round trip failed: original and reparsed models are not equal"
|
75
|
-
|
76
|
-
# Assert that all fields are present and have the correct types
|
77
|
-
for field, field_type in model_class.__annotations__.items():
|
78
|
-
assert hasattr(parsed_model, field), f"Field {field} is missing"
|
79
|
-
assert isinstance(getattr(parsed_model, field), field_type), f"Field {field} has incorrect type"
|
80
|
-
|
81
|
-
print("Round trip test passed successfully!")
|
82
|
-
|
83
|
-
if __name__ == "__main__":
|
84
|
-
# Test case 1: Standard formatting
|
85
|
-
test_string1 = """
|
86
|
-
<spec>
|
87
|
-
name: John Doe
|
88
|
-
age is 30
|
89
|
-
lives in New York
|
90
|
-
</spec>
|
91
|
-
"""
|
92
|
-
test_round_trip(PersonModel, test_string1)
|
93
|
-
|
94
|
-
print("\n" + "="*50 + "\n")
|
95
|
-
|
96
|
-
# Test case 2: Varying whitespace
|
97
|
-
test_string2 = "<spec>name: Alice \nage is 25 \nlives in Tokyo</spec>"
|
98
|
-
test_round_trip(PersonModel, test_string2)
|
99
|
-
|
100
|
-
print("\n" + "="*50 + "\n")
|
101
|
-
|
102
|
-
# Test case 3: Multiline values
|
103
|
-
test_string3 = """
|
104
|
-
<spec>
|
105
|
-
name: Bob
|
106
|
-
Smith
|
107
|
-
age is 40
|
108
|
-
lives in San
|
109
|
-
Francisco
|
110
|
-
</spec>
|
111
|
-
"""
|
112
|
-
test_round_trip(PersonModel, test_string3)
|
113
|
-
|
114
|
-
print("All tests passed successfully!")
|
115
|
-
|
116
|
-
|
@@ -1,80 +0,0 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
from typing import ClassVar, Dict
|
3
|
-
|
4
|
-
from tatsu import compile as compile_grammar
|
5
|
-
from tatsu.model import ModelBuilder
|
6
|
-
|
7
|
-
from langroid.pydantic_v1 import BaseModel
|
8
|
-
|
9
|
-
|
10
|
-
class GrammarBasedModel(BaseModel, ABC):
|
11
|
-
grammar: ClassVar[str]
|
12
|
-
start_token: ClassVar[str]
|
13
|
-
end_token: ClassVar[str]
|
14
|
-
field_mappings: ClassVar[Dict[str, str]]
|
15
|
-
|
16
|
-
@classmethod
|
17
|
-
@abstractmethod
|
18
|
-
def get_grammar(cls) -> str:
|
19
|
-
pass
|
20
|
-
|
21
|
-
@classmethod
|
22
|
-
def parse(cls, text: str) -> "GrammarBasedModel":
|
23
|
-
parser = compile_grammar(cls.get_grammar())
|
24
|
-
ast = parser.parse(text, start="start")
|
25
|
-
model_dict = {
|
26
|
-
field: getattr(ast, rule) for field, rule in cls.field_mappings.items()
|
27
|
-
}
|
28
|
-
return cls(**model_dict)
|
29
|
-
|
30
|
-
def generate(self) -> str:
|
31
|
-
grammar = compile_grammar(self.get_grammar())
|
32
|
-
model_builder = ModelBuilder()
|
33
|
-
for field, rule in self.field_mappings.items():
|
34
|
-
setattr(model_builder, rule, getattr(self, field))
|
35
|
-
ast = model_builder.start()
|
36
|
-
return f"{self.start_token}\n{grammar.parse(str(ast), start='start')}\n{self.end_token}"
|
37
|
-
|
38
|
-
|
39
|
-
class PersonSpec(GrammarBasedModel):
|
40
|
-
name: str
|
41
|
-
age: int
|
42
|
-
city: str
|
43
|
-
|
44
|
-
grammar = """
|
45
|
-
start = name_line age_line city_line;
|
46
|
-
name_line = 'name:' /\s*/ name:/.+/ EOL;
|
47
|
-
age_line = 'age is' /\s*/ age:/\d+/ EOL;
|
48
|
-
city_line = 'lives in' /\s*/ city:/.+/ EOL;
|
49
|
-
EOL = /\r?\n/;
|
50
|
-
"""
|
51
|
-
start_token = "<spec>"
|
52
|
-
end_token = "</spec>"
|
53
|
-
field_mappings = {"name": "name", "age": "age", "city": "city"}
|
54
|
-
|
55
|
-
@classmethod
|
56
|
-
def get_grammar(cls):
|
57
|
-
return cls.grammar
|
58
|
-
|
59
|
-
|
60
|
-
if __name__ == "__main__":
|
61
|
-
# Test parsing
|
62
|
-
input_str = """<spec>
|
63
|
-
name: John Doe
|
64
|
-
age is 30
|
65
|
-
lives in New York
|
66
|
-
</spec>"""
|
67
|
-
person = PersonSpec.parse(input_str)
|
68
|
-
print("Parsed person:", person)
|
69
|
-
|
70
|
-
# Test generation
|
71
|
-
generated_str = person.generate()
|
72
|
-
print("\nGenerated string:")
|
73
|
-
print(generated_str)
|
74
|
-
|
75
|
-
# Test round-trip
|
76
|
-
round_trip_person = PersonSpec.parse(generated_str)
|
77
|
-
print("\nRound-trip parsed person:", round_trip_person)
|
78
|
-
|
79
|
-
assert person == round_trip_person, "Round-trip parsing failed"
|
80
|
-
print("\nRound-trip test passed!")
|
@@ -1,77 +0,0 @@
|
|
1
|
-
from typing import Dict, Type, ClassVar
|
2
|
-
from pydantic import BaseModel
|
3
|
-
from tatsu import compile as compile_grammar
|
4
|
-
from tatsu.model import ModelBuilder
|
5
|
-
from abc import ABC, abstractmethod
|
6
|
-
|
7
|
-
class GrammarBasedModel(BaseModel, ABC):
|
8
|
-
grammar: ClassVar[str]
|
9
|
-
start_token: ClassVar[str]
|
10
|
-
end_token: ClassVar[str]
|
11
|
-
field_mappings: ClassVar[Dict[str, str]]
|
12
|
-
|
13
|
-
@classmethod
|
14
|
-
@abstractmethod
|
15
|
-
def get_grammar(cls) -> str:
|
16
|
-
pass
|
17
|
-
|
18
|
-
@classmethod
|
19
|
-
def parse(cls, text: str) -> 'GrammarBasedModel':
|
20
|
-
parser = compile_grammar(cls.get_grammar())
|
21
|
-
ast = parser.parse(text, start='start')
|
22
|
-
model_dict = {field: getattr(ast, rule) for field, rule in cls.field_mappings.items()}
|
23
|
-
return cls(**model_dict)
|
24
|
-
|
25
|
-
def generate(self) -> str:
|
26
|
-
grammar = compile_grammar(self.get_grammar())
|
27
|
-
model_builder = ModelBuilder()
|
28
|
-
for field, rule in self.field_mappings.items():
|
29
|
-
setattr(model_builder, rule, getattr(self, field))
|
30
|
-
ast = model_builder.start()
|
31
|
-
return f"{self.start_token}\n{grammar.parse(str(ast), start='start')}\n{self.end_token}"
|
32
|
-
|
33
|
-
class PersonSpec(GrammarBasedModel):
|
34
|
-
name: str
|
35
|
-
age: int
|
36
|
-
city: str
|
37
|
-
|
38
|
-
grammar = """
|
39
|
-
start = name_line age_line city_line;
|
40
|
-
name_line = 'name:' /\s*/ name:/.+/ EOL;
|
41
|
-
age_line = 'age is' /\s*/ age:/\d+/ EOL;
|
42
|
-
city_line = 'lives in' /\s*/ city:/.+/ EOL;
|
43
|
-
EOL = /\r?\n/;
|
44
|
-
"""
|
45
|
-
start_token = "<spec>"
|
46
|
-
end_token = "</spec>"
|
47
|
-
field_mappings = {
|
48
|
-
"name": "name",
|
49
|
-
"age": "age",
|
50
|
-
"city": "city"
|
51
|
-
}
|
52
|
-
|
53
|
-
@classmethod
|
54
|
-
def get_grammar(cls):
|
55
|
-
return cls.grammar
|
56
|
-
|
57
|
-
if __name__ == "__main__":
|
58
|
-
# Test parsing
|
59
|
-
input_str = """<spec>
|
60
|
-
name: John Doe
|
61
|
-
age is 30
|
62
|
-
lives in New York
|
63
|
-
</spec>"""
|
64
|
-
person = PersonSpec.parse(input_str)
|
65
|
-
print("Parsed person:", person)
|
66
|
-
|
67
|
-
# Test generation
|
68
|
-
generated_str = person.generate()
|
69
|
-
print("\nGenerated string:")
|
70
|
-
print(generated_str)
|
71
|
-
|
72
|
-
# Test round-trip
|
73
|
-
round_trip_person = PersonSpec.parse(generated_str)
|
74
|
-
print("\nRound-trip parsed person:", round_trip_person)
|
75
|
-
|
76
|
-
assert person == round_trip_person, "Round-trip parsing failed"
|
77
|
-
print("\nRound-trip test passed!")
|
@@ -1,135 +0,0 @@
|
|
1
|
-
import pytest
|
2
|
-
from lark import Lark, Transformer, Visitor
|
3
|
-
|
4
|
-
from langroid.pydantic_v1 import BaseModel
|
5
|
-
|
6
|
-
|
7
|
-
class Person(BaseModel):
|
8
|
-
name: str
|
9
|
-
age: int
|
10
|
-
city: str
|
11
|
-
|
12
|
-
|
13
|
-
grammar = """
|
14
|
-
person: "Name:" name "Age:" age "City:" city
|
15
|
-
name: WORD
|
16
|
-
age: NUMBER
|
17
|
-
city: WORD
|
18
|
-
WORD: /\w+/
|
19
|
-
NUMBER: /\d+/
|
20
|
-
%import common.WS
|
21
|
-
%ignore WS
|
22
|
-
"""
|
23
|
-
|
24
|
-
parser = Lark(grammar, start="person", parser="earley")
|
25
|
-
|
26
|
-
|
27
|
-
class PersonTransformer(Transformer):
|
28
|
-
def person(self, items):
|
29
|
-
return Person(name=items[1], age=items[3], city=items[5])
|
30
|
-
|
31
|
-
def name(self, items):
|
32
|
-
return items[0].value
|
33
|
-
|
34
|
-
def age(self, items):
|
35
|
-
return int(items[0].value)
|
36
|
-
|
37
|
-
def city(self, items):
|
38
|
-
return items[0].value
|
39
|
-
|
40
|
-
|
41
|
-
class PersonToStringVisitor(Visitor):
|
42
|
-
def __init__(self):
|
43
|
-
self.result = []
|
44
|
-
|
45
|
-
def person(self, tree):
|
46
|
-
self.visit_children(tree)
|
47
|
-
return " ".join(self.result)
|
48
|
-
|
49
|
-
def name(self, tree):
|
50
|
-
self.result.extend(["Name:", str(tree.children[0])])
|
51
|
-
|
52
|
-
def age(self, tree):
|
53
|
-
self.result.extend(["Age:", str(tree.children[0])])
|
54
|
-
|
55
|
-
def city(self, tree):
|
56
|
-
self.result.extend(["City:", str(tree.children[0])])
|
57
|
-
|
58
|
-
|
59
|
-
transformer = PersonTransformer()
|
60
|
-
to_string_visitor = PersonToStringVisitor()
|
61
|
-
|
62
|
-
|
63
|
-
def from_string(cls, string):
|
64
|
-
tree = parser.parse(string)
|
65
|
-
return transformer.transform(tree)
|
66
|
-
|
67
|
-
|
68
|
-
def to_string(self):
|
69
|
-
from lark import Token
|
70
|
-
|
71
|
-
tree = parser.parse(
|
72
|
-
f"Name: {Token('WORD', self.name)} "
|
73
|
-
f"Age: {Token('NUMBER', str(self.age))} "
|
74
|
-
f"City: {Token('WORD', self.city)}"
|
75
|
-
)
|
76
|
-
return to_string_visitor.visit(tree)
|
77
|
-
|
78
|
-
|
79
|
-
Person.from_string = classmethod(from_string)
|
80
|
-
Person.to_string = to_string
|
81
|
-
|
82
|
-
# Test functions remain the same as in the previous version
|
83
|
-
|
84
|
-
|
85
|
-
# Test functions
|
86
|
-
def test_from_string():
|
87
|
-
person_str = "Name: John Age: 30 City: NewYork"
|
88
|
-
person = Person.from_string(person_str)
|
89
|
-
assert person.name == "John"
|
90
|
-
assert person.age == 30
|
91
|
-
assert person.city == "NewYork"
|
92
|
-
|
93
|
-
|
94
|
-
def test_to_string():
|
95
|
-
person = Person(name="Alice", age=25, city="London")
|
96
|
-
person_str = person.to_string()
|
97
|
-
assert person_str == "Name: Alice Age: 25 City: London"
|
98
|
-
|
99
|
-
|
100
|
-
def test_roundtrip():
|
101
|
-
original_str = "Name: Bob Age: 40 City: Paris"
|
102
|
-
person = Person.from_string(original_str)
|
103
|
-
regenerated_str = person.to_string()
|
104
|
-
assert original_str == regenerated_str
|
105
|
-
|
106
|
-
|
107
|
-
def test_different_values():
|
108
|
-
person = Person(name="Charlie", age=35, city="Berlin")
|
109
|
-
person_str = person.to_string()
|
110
|
-
assert person_str == "Name: Charlie Age: 35 City: Berlin"
|
111
|
-
|
112
|
-
|
113
|
-
def test_edge_cases():
|
114
|
-
# Test with minimum age
|
115
|
-
person = Person(name="Young", age=0, city="Baby")
|
116
|
-
person_str = person.to_string()
|
117
|
-
assert person_str == "Name: Young Age: 0 City: Baby"
|
118
|
-
|
119
|
-
# Test with very long name and city
|
120
|
-
long_name = "A" * 100
|
121
|
-
long_city = "B" * 100
|
122
|
-
person = Person(name=long_name, age=50, city=long_city)
|
123
|
-
person_str = person.to_string()
|
124
|
-
assert person_str == f"Name: {long_name} Age: 50 City: {long_city}"
|
125
|
-
|
126
|
-
|
127
|
-
def test_invalid_input():
|
128
|
-
with pytest.raises(
|
129
|
-
Exception
|
130
|
-
): # The exact exception may vary based on Lark's implementation
|
131
|
-
Person.from_string("Invalid: Input")
|
132
|
-
|
133
|
-
|
134
|
-
if __name__ == "__main__":
|
135
|
-
pytest.main([__file__])
|
@@ -1,117 +0,0 @@
|
|
1
|
-
import pytest
|
2
|
-
from pydantic import BaseModel
|
3
|
-
from lark import Lark, Transformer, Visitor
|
4
|
-
|
5
|
-
class Person(BaseModel):
|
6
|
-
name: str
|
7
|
-
age: int
|
8
|
-
city: str
|
9
|
-
|
10
|
-
grammar = """
|
11
|
-
person: "Name:" name "Age:" age "City:" city
|
12
|
-
name: WORD
|
13
|
-
age: NUMBER
|
14
|
-
city: WORD
|
15
|
-
WORD: /\w+/
|
16
|
-
NUMBER: /\d+/
|
17
|
-
%import common.WS
|
18
|
-
%ignore WS
|
19
|
-
"""
|
20
|
-
|
21
|
-
parser = Lark(grammar, start='person', parser='earley')
|
22
|
-
|
23
|
-
class PersonTransformer(Transformer):
|
24
|
-
def person(self, items):
|
25
|
-
return Person(name=items[1], age=items[3], city=items[5])
|
26
|
-
|
27
|
-
def name(self, items):
|
28
|
-
return items[0].value
|
29
|
-
|
30
|
-
def age(self, items):
|
31
|
-
return int(items[0].value)
|
32
|
-
|
33
|
-
def city(self, items):
|
34
|
-
return items[0].value
|
35
|
-
|
36
|
-
class PersonToStringVisitor(Visitor):
|
37
|
-
def __init__(self):
|
38
|
-
self.result = []
|
39
|
-
|
40
|
-
def person(self, tree):
|
41
|
-
self.visit_children(tree)
|
42
|
-
return " ".join(self.result)
|
43
|
-
|
44
|
-
def name(self, tree):
|
45
|
-
self.result.extend(["Name:", str(tree.children[0])])
|
46
|
-
|
47
|
-
def age(self, tree):
|
48
|
-
self.result.extend(["Age:", str(tree.children[0])])
|
49
|
-
|
50
|
-
def city(self, tree):
|
51
|
-
self.result.extend(["City:", str(tree.children[0])])
|
52
|
-
|
53
|
-
transformer = PersonTransformer()
|
54
|
-
to_string_visitor = PersonToStringVisitor()
|
55
|
-
|
56
|
-
def from_string(cls, string):
|
57
|
-
tree = parser.parse(string)
|
58
|
-
return transformer.transform(tree)
|
59
|
-
|
60
|
-
def to_string(self):
|
61
|
-
from lark import Token
|
62
|
-
tree = parser.parse(
|
63
|
-
f"Name: {Token('WORD', self.name)} "
|
64
|
-
f"Age: {Token('NUMBER', str(self.age))} "
|
65
|
-
f"City: {Token('WORD', self.city)}"
|
66
|
-
)
|
67
|
-
return to_string_visitor.visit(tree)
|
68
|
-
|
69
|
-
Person.from_string = classmethod(from_string)
|
70
|
-
Person.to_string = to_string
|
71
|
-
|
72
|
-
# Test functions remain the same as in the previous version
|
73
|
-
|
74
|
-
# Test functions
|
75
|
-
def test_from_string():
|
76
|
-
person_str = "Name: John Age: 30 City: NewYork"
|
77
|
-
person = Person.from_string(person_str)
|
78
|
-
assert person.name == "John"
|
79
|
-
assert person.age == 30
|
80
|
-
assert person.city == "NewYork"
|
81
|
-
|
82
|
-
def test_to_string():
|
83
|
-
person = Person(name="Alice", age=25, city="London")
|
84
|
-
person_str = person.to_string()
|
85
|
-
assert person_str == "Name: Alice Age: 25 City: London"
|
86
|
-
|
87
|
-
def test_roundtrip():
|
88
|
-
original_str = "Name: Bob Age: 40 City: Paris"
|
89
|
-
person = Person.from_string(original_str)
|
90
|
-
regenerated_str = person.to_string()
|
91
|
-
assert original_str == regenerated_str
|
92
|
-
|
93
|
-
def test_different_values():
|
94
|
-
person = Person(name="Charlie", age=35, city="Berlin")
|
95
|
-
person_str = person.to_string()
|
96
|
-
assert person_str == "Name: Charlie Age: 35 City: Berlin"
|
97
|
-
|
98
|
-
def test_edge_cases():
|
99
|
-
# Test with minimum age
|
100
|
-
person = Person(name="Young", age=0, city="Baby")
|
101
|
-
person_str = person.to_string()
|
102
|
-
assert person_str == "Name: Young Age: 0 City: Baby"
|
103
|
-
|
104
|
-
# Test with very long name and city
|
105
|
-
long_name = "A" * 100
|
106
|
-
long_city = "B" * 100
|
107
|
-
person = Person(name=long_name, age=50, city=long_city)
|
108
|
-
person_str = person.to_string()
|
109
|
-
assert person_str == f"Name: {long_name} Age: 50 City: {long_city}"
|
110
|
-
|
111
|
-
def test_invalid_input():
|
112
|
-
with pytest.raises(Exception): # The exact exception may vary based on Lark's implementation
|
113
|
-
Person.from_string("Invalid: Input")
|
114
|
-
|
115
|
-
if __name__ == "__main__":
|
116
|
-
pytest.main([__file__])
|
117
|
-
|