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.
Files changed (91) hide show
  1. langroid/agent/base.py +45 -21
  2. langroid/agent/chat_agent.py +22 -14
  3. langroid/agent/chat_document.py +22 -13
  4. langroid/agent/tool_message.py +11 -11
  5. langroid/agent/tools/file_tools.py +234 -0
  6. langroid/agent/xml_tool_message.py +178 -45
  7. langroid/utils/constants.py +2 -0
  8. langroid/utils/git_utils.py +251 -0
  9. langroid/utils/system.py +78 -0
  10. {langroid-0.16.7.dist-info → langroid-0.17.0.dist-info}/METADATA +6 -3
  11. {langroid-0.16.7.dist-info → langroid-0.17.0.dist-info}/RECORD +14 -89
  12. pyproject.toml +3 -2
  13. langroid/agent/md_tool_message_grammar.py +0 -455
  14. langroid/agent/tools/code_file_tool_parse.py +0 -150
  15. langroid/agent/tools/code_file_tool_pyparsing.py +0 -194
  16. langroid/agent/tools/code_file_tool_pyparsing2.py +0 -199
  17. langroid/agent/tools/extract_tool.py +0 -96
  18. langroid/agent/tools/formatted_model_custom.py +0 -150
  19. langroid/agent/tools/formatted_model_custom2.py +0 -168
  20. langroid/agent/tools/formatted_model_custom3.py +0 -279
  21. langroid/agent/tools/formatted_model_custom4.py +0 -395
  22. langroid/agent/tools/formatted_model_jinja.py +0 -133
  23. langroid/agent/tools/formatted_model_jinja.py-e +0 -122
  24. langroid/agent/tools/formatted_model_jinja2.py +0 -145
  25. langroid/agent/tools/formatted_model_jinja2.py-e +0 -135
  26. langroid/agent/tools/formatted_model_lark.py +0 -0
  27. langroid/agent/tools/formatted_model_lark2.py +0 -168
  28. langroid/agent/tools/formatted_model_parse.py +0 -105
  29. langroid/agent/tools/formatted_model_parse.py-e +0 -98
  30. langroid/agent/tools/formatted_model_parse2.py +0 -113
  31. langroid/agent/tools/formatted_model_parse2.py-e +0 -109
  32. langroid/agent/tools/formatted_model_parse3.py +0 -114
  33. langroid/agent/tools/formatted_model_parse3.py-e +0 -110
  34. langroid/agent/tools/formatted_model_parsimon.py +0 -194
  35. langroid/agent/tools/formatted_model_parsimon.py-e +0 -186
  36. langroid/agent/tools/formatted_model_pyparsing.py +0 -169
  37. langroid/agent/tools/formatted_model_pyparsing.py-e +0 -149
  38. langroid/agent/tools/formatted_model_pyparsing2.py +0 -159
  39. langroid/agent/tools/formatted_model_pyparsing2.py-e +0 -143
  40. langroid/agent/tools/formatted_model_pyparsing3.py +0 -133
  41. langroid/agent/tools/formatted_model_pyparsing3.py-e +0 -121
  42. langroid/agent/tools/formatted_model_pyparsing4.py +0 -213
  43. langroid/agent/tools/formatted_model_pyparsing4.py-e +0 -176
  44. langroid/agent/tools/formatted_model_pyparsing5.py +0 -173
  45. langroid/agent/tools/formatted_model_pyparsing5.py-e +0 -142
  46. langroid/agent/tools/formatted_model_regex.py +0 -246
  47. langroid/agent/tools/formatted_model_regex.py-e +0 -248
  48. langroid/agent/tools/formatted_model_regex2.py +0 -250
  49. langroid/agent/tools/formatted_model_regex2.py-e +0 -253
  50. langroid/agent/tools/formatted_model_tatsu.py +0 -172
  51. langroid/agent/tools/formatted_model_tatsu.py-e +0 -160
  52. langroid/agent/tools/formatted_model_template.py +0 -217
  53. langroid/agent/tools/formatted_model_template.py-e +0 -200
  54. langroid/agent/tools/formatted_model_xml.py +0 -178
  55. langroid/agent/tools/formatted_model_xml2.py +0 -178
  56. langroid/agent/tools/formatted_model_xml3.py +0 -132
  57. langroid/agent/tools/formatted_model_xml4.py +0 -130
  58. langroid/agent/tools/formatted_model_xml5.py +0 -130
  59. langroid/agent/tools/formatted_model_xml6.py +0 -113
  60. langroid/agent/tools/formatted_model_xml7.py +0 -117
  61. langroid/agent/tools/formatted_model_xml8.py +0 -164
  62. langroid/agent/tools/generator_tool.py +0 -20
  63. langroid/agent/tools/generic_tool.py +0 -165
  64. langroid/agent/tools/generic_tool_tatsu.py +0 -275
  65. langroid/agent/tools/grammar_based_model.py +0 -132
  66. langroid/agent/tools/grammar_based_model.py-e +0 -128
  67. langroid/agent/tools/grammar_based_model_lark.py +0 -156
  68. langroid/agent/tools/grammar_based_model_lark.py-e +0 -153
  69. langroid/agent/tools/grammar_based_model_parse.py +0 -86
  70. langroid/agent/tools/grammar_based_model_parse.py-e +0 -80
  71. langroid/agent/tools/grammar_based_model_parsimonious.py +0 -129
  72. langroid/agent/tools/grammar_based_model_parsimonious.py-e +0 -120
  73. langroid/agent/tools/grammar_based_model_pyparsing.py +0 -105
  74. langroid/agent/tools/grammar_based_model_pyparsing.py-e +0 -103
  75. langroid/agent/tools/grammar_based_model_regex.py +0 -139
  76. langroid/agent/tools/grammar_based_model_regex.py-e +0 -130
  77. langroid/agent/tools/grammar_based_model_regex2.py +0 -124
  78. langroid/agent/tools/grammar_based_model_regex2.py-e +0 -116
  79. langroid/agent/tools/grammar_based_model_tatsu.py +0 -80
  80. langroid/agent/tools/grammar_based_model_tatsu.py-e +0 -77
  81. langroid/agent/tools/lark_earley_example.py +0 -135
  82. langroid/agent/tools/lark_earley_example.py-e +0 -117
  83. langroid/agent/tools/lark_example.py +0 -72
  84. langroid/agent/tools/note_tool.py +0 -0
  85. langroid/agent/tools/parse_example.py +0 -76
  86. langroid/agent/tools/parse_example2.py +0 -87
  87. langroid/agent/tools/parse_example3.py +0 -42
  88. langroid/agent/tools/parse_test.py +0 -791
  89. langroid/agent/tools/run_python_code.py +0 -60
  90. {langroid-0.16.7.dist-info → langroid-0.17.0.dist-info}/LICENSE +0 -0
  91. {langroid-0.16.7.dist-info → langroid-0.17.0.dist-info}/WHEEL +0 -0
@@ -1,145 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import Type, TypeVar
3
-
4
- from jinja2 import BaseLoader, Environment
5
- from parse import parse as str_parse
6
- from parse import with_pattern
7
-
8
- from langroid.pydantic_v1 import BaseModel
9
-
10
- T = TypeVar("T", bound="FormattingModel")
11
-
12
-
13
- @with_pattern(r"[\s\S]*?")
14
- def _match_multiline(text):
15
- return text.strip()
16
-
17
-
18
- class FormattingModel(BaseModel, ABC):
19
- @classmethod
20
- @abstractmethod
21
- def format_spec(cls) -> str:
22
- pass
23
-
24
- @classmethod
25
- @abstractmethod
26
- def parse_spec(cls) -> str:
27
- pass
28
-
29
- @classmethod
30
- @abstractmethod
31
- def start_token(cls) -> str:
32
- pass
33
-
34
- @classmethod
35
- @abstractmethod
36
- def end_token(cls) -> str:
37
- pass
38
-
39
- @classmethod
40
- def parse(cls: Type[T], text: str) -> T:
41
- content = text.strip()[len(cls.start_token()) : -len(cls.end_token())].strip()
42
- result = str_parse(
43
- cls.parse_spec(),
44
- content,
45
- dict(multiline=_match_multiline),
46
- case_sensitive=False,
47
- )
48
- if result is None:
49
- raise ValueError(
50
- f"Failed to parse the input string using spec: {cls.parse_spec()}\nInput: {content}"
51
- )
52
- return cls(**result.named)
53
-
54
- def generate(self) -> str:
55
- env = Environment(loader=BaseLoader(), trim_blocks=True, lstrip_blocks=True)
56
- template = env.from_string(self.format_spec())
57
- content = template.render(**self.dict())
58
- return f"{self.start_token()}\n{content.strip()}\n{self.end_token()}"
59
-
60
-
61
- class CodeFileModel(FormattingModel):
62
- language: str
63
- file_path: str
64
- code: str
65
-
66
- @classmethod
67
- def format_spec(cls) -> str:
68
- return (
69
- "code_file_model\n"
70
- "file_path: {{- file_path -}}\n"
71
- "```{{- language -}}\n"
72
- "{{ code }}"
73
- "```"
74
- )
75
-
76
- @classmethod
77
- def parse_spec(cls) -> str:
78
- return "code_file_model\n" "file_path:{:s}\n" "```{:s}\n" "{:multiline}" "```"
79
-
80
- @classmethod
81
- def start_token(cls) -> str:
82
- return "<format>"
83
-
84
- @classmethod
85
- def end_token(cls) -> str:
86
- return "</format>"
87
-
88
-
89
- # Test code
90
- if __name__ == "__main__":
91
- # Test with extra whitespace
92
- test_string = """
93
- <format>
94
- code_file_model
95
- file_path: src/main.py
96
- ``` python
97
- def hello():
98
- print("Hello, World!")
99
-
100
- ```
101
- </format>
102
- """
103
-
104
- parsed = CodeFileModel.parse(test_string)
105
- print("Parsed model:")
106
- print(parsed)
107
-
108
- generated = parsed.generate()
109
- print("\nGenerated string:")
110
- print(generated)
111
-
112
- reparsed = CodeFileModel.parse(generated)
113
- print("\nReparsed model:")
114
- print(reparsed)
115
-
116
- print("\nRound trip test:")
117
- assert parsed == reparsed, "Round trip test failed"
118
- print("Passed!")
119
-
120
- # Test with different values and whitespace
121
- another_test = """
122
- <format>
123
- code_file_model
124
- file_path:src/app.js
125
- ``` javascript
126
- function greet(name) {
127
- console.log(`Hello, ${name}!`);
128
- }
129
- ```
130
- </format>
131
- """
132
-
133
- another_parsed = CodeFileModel.parse(another_test)
134
- print("\nAnother parsed model:")
135
- print(another_parsed)
136
-
137
- another_generated = another_parsed.generate()
138
- print("\nAnother generated string:")
139
- print(another_generated)
140
-
141
- print("\nAnother round trip test:")
142
- assert another_parsed == CodeFileModel.parse(
143
- another_generated
144
- ), "Another round trip test failed"
145
- print("Passed!")
@@ -1,135 +0,0 @@
1
- from pydantic import BaseModel
2
- from abc import ABC, abstractmethod
3
- from typing import Type, TypeVar
4
- from jinja2 import Environment, BaseLoader
5
- from parse import parse as str_parse, with_pattern
6
-
7
- T = TypeVar('T', bound='FormattingModel')
8
-
9
- @with_pattern(r'[\s\S]*?')
10
- def _match_multiline(text):
11
- return text.strip()
12
-
13
- class FormattingModel(BaseModel, ABC):
14
- @classmethod
15
- @abstractmethod
16
- def format_spec(cls) -> str:
17
- pass
18
-
19
- @classmethod
20
- @abstractmethod
21
- def parse_spec(cls) -> str:
22
- pass
23
-
24
- @classmethod
25
- @abstractmethod
26
- def start_token(cls) -> str:
27
- pass
28
-
29
- @classmethod
30
- @abstractmethod
31
- def end_token(cls) -> str:
32
- pass
33
-
34
- @classmethod
35
- def parse(cls: Type[T], text: str) -> T:
36
- content = text.strip()[len(cls.start_token()):-len(cls.end_token())].strip()
37
- result = str_parse(cls.parse_spec(), content, dict(multiline=_match_multiline), case_sensitive=False)
38
- if result is None:
39
- raise ValueError(f"Failed to parse the input string using spec: {cls.parse_spec()}\nInput: {content}")
40
- return cls(**result.named)
41
-
42
- def generate(self) -> str:
43
- env = Environment(loader=BaseLoader(), trim_blocks=True, lstrip_blocks=True)
44
- template = env.from_string(self.format_spec())
45
- content = template.render(**self.dict())
46
- return f"{self.start_token()}\n{content.strip()}\n{self.end_token()}"
47
-
48
- class CodeFileModel(FormattingModel):
49
- language: str
50
- file_path: str
51
- code: str
52
-
53
- @classmethod
54
- def format_spec(cls) -> str:
55
- return (
56
- "code_file_model\n"
57
- "file_path: {{- file_path -}}\n"
58
- "```{{- language -}}\n"
59
- "{{ code }}"
60
- "```"
61
- )
62
-
63
- @classmethod
64
- def parse_spec(cls) -> str:
65
- return (
66
- "code_file_model\n"
67
- "file_path:{:s}\n"
68
- "```{:s}\n"
69
- "{:multiline}"
70
- "```"
71
- )
72
-
73
- @classmethod
74
- def start_token(cls) -> str:
75
- return "<format>"
76
-
77
- @classmethod
78
- def end_token(cls) -> str:
79
- return "</format>"
80
-
81
- # Test code
82
- if __name__ == "__main__":
83
- # Test with extra whitespace
84
- test_string = """
85
- <format>
86
- code_file_model
87
- file_path: src/main.py
88
- ``` python
89
- def hello():
90
- print("Hello, World!")
91
-
92
- ```
93
- </format>
94
- """
95
-
96
- parsed = CodeFileModel.parse(test_string)
97
- print("Parsed model:")
98
- print(parsed)
99
-
100
- generated = parsed.generate()
101
- print("\nGenerated string:")
102
- print(generated)
103
-
104
- reparsed = CodeFileModel.parse(generated)
105
- print("\nReparsed model:")
106
- print(reparsed)
107
-
108
- print("\nRound trip test:")
109
- assert parsed == reparsed, "Round trip test failed"
110
- print("Passed!")
111
-
112
- # Test with different values and whitespace
113
- another_test = """
114
- <format>
115
- code_file_model
116
- file_path:src/app.js
117
- ``` javascript
118
- function greet(name) {
119
- console.log(`Hello, ${name}!`);
120
- }
121
- ```
122
- </format>
123
- """
124
-
125
- another_parsed = CodeFileModel.parse(another_test)
126
- print("\nAnother parsed model:")
127
- print(another_parsed)
128
-
129
- another_generated = another_parsed.generate()
130
- print("\nAnother generated string:")
131
- print(another_generated)
132
-
133
- print("\nAnother round trip test:")
134
- assert another_parsed == CodeFileModel.parse(another_generated), "Another round trip test failed"
135
- print("Passed!")
File without changes
@@ -1,168 +0,0 @@
1
- from abc import ABC, abstractmethod
2
-
3
- from lark import Lark, Transformer, v_args
4
-
5
- from langroid.pydantic_v1 import BaseModel
6
-
7
-
8
- class FormattingModel(BaseModel, ABC):
9
- @classmethod
10
- @abstractmethod
11
- def format_spec(cls) -> str:
12
- pass
13
-
14
- @classmethod
15
- @abstractmethod
16
- def parse_spec(cls) -> str:
17
- pass
18
-
19
- @classmethod
20
- @abstractmethod
21
- def start_token(cls) -> str:
22
- pass
23
-
24
- @classmethod
25
- @abstractmethod
26
- def end_token(cls) -> str:
27
- pass
28
-
29
- @classmethod
30
- def format(cls, instance: "FormattingModel") -> str:
31
- spec = cls.format_spec()
32
- formatted = spec.format(**instance.dict())
33
- return f"{cls.start_token()}\n{formatted}\n{cls.end_token()}"
34
-
35
- @classmethod
36
- def parse(cls, formatted_string: str) -> "FormattingModel":
37
- lines = formatted_string.strip().split("\n")
38
- if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
39
- raise ValueError("Invalid start or end token")
40
- content = "\n".join(lines[1:-1])
41
-
42
- parser = Lark(cls.parse_spec(), start="start", parser="lalr")
43
-
44
- @v_args(inline=True)
45
- class TreeToDict(Transformer):
46
- def start(self, _, file_path, code_block):
47
- return {
48
- "file_path": file_path,
49
- "language": code_block.children[1],
50
- "code": code_block.children[3],
51
- }
52
-
53
- def file_path(self, path):
54
- return path.value
55
-
56
- def language(self, lang):
57
- return lang.value
58
-
59
- def code(self, code):
60
- return code.value.strip()
61
-
62
- tree = parser.parse(content)
63
- data = TreeToDict().transform(tree)
64
- return cls(**data)
65
-
66
-
67
- class CodeFileModel(FormattingModel):
68
- language: str
69
- file_path: str
70
- code: str
71
-
72
- @classmethod
73
- def format_spec(cls):
74
- return "code_file_model\n{file_path}\n```{language}\n{code}\n```"
75
-
76
- @classmethod
77
- def parse_spec(cls):
78
- return """
79
- start: "code_file_model" NEWLINE file_path NEWLINE code_block
80
- file_path: /[^\\n]+/
81
- code_block: "```" language NEWLINE code "```"
82
- language: /[^\\n]+/
83
- code: /.+?(?=\\n```)/s
84
- NEWLINE: "\\n"
85
- %import common.WS
86
- %ignore WS
87
- """
88
-
89
- @classmethod
90
- def start_token(cls):
91
- return "<format>"
92
-
93
- @classmethod
94
- def end_token(cls):
95
- return "</format>"
96
-
97
-
98
- # Test cases
99
- if __name__ == "__main__":
100
- # Test formatting
101
- code_file = CodeFileModel(
102
- language="Python",
103
- file_path="src/main.py",
104
- code="def hello():\n print('Hello, World!')",
105
- )
106
- formatted = CodeFileModel.format(code_file)
107
- expected_format = """<format>
108
- code_file_model
109
- src/main.py
110
- ```Python
111
- def hello():
112
- print('Hello, World!')
113
- ```
114
- </format>"""
115
- assert (
116
- formatted == expected_format
117
- ), f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
118
- print("Formatting test passed.")
119
-
120
- # Test parsing
121
- parsed = CodeFileModel.parse(formatted)
122
- assert (
123
- parsed == code_file
124
- ), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
125
- print("Parsing test passed.")
126
-
127
- # Test round-trip
128
- round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
129
- assert (
130
- round_trip == code_file
131
- ), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
132
- print("Round-trip test passed.")
133
-
134
- # Test with different values
135
- code_file2 = CodeFileModel(
136
- language="JavaScript",
137
- file_path="src/app.js",
138
- code="function greet() {\n console.log('Hello, World!');\n}",
139
- )
140
- formatted2 = CodeFileModel.format(code_file2)
141
- parsed2 = CodeFileModel.parse(formatted2)
142
- assert (
143
- parsed2 == code_file2
144
- ), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
145
- print("Different values test passed.")
146
-
147
- # Test tolerant parsing
148
- tolerant_input = """<format>
149
- code_file_model
150
- src/main.py
151
-
152
- ``` Python
153
- def hello():
154
- print('Hello, World!')
155
- ```
156
- </format>"""
157
- parsed_tolerant = CodeFileModel.parse(tolerant_input)
158
- expected_tolerant = CodeFileModel(
159
- language="Python",
160
- file_path="src/main.py",
161
- code="def hello():\n print('Hello, World!')",
162
- )
163
- assert (
164
- parsed_tolerant == expected_tolerant
165
- ), f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
166
- print("Tolerant parsing test passed.")
167
-
168
- print("All tests passed successfully!")
@@ -1,105 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import ClassVar
3
-
4
- from parse import parse
5
-
6
- from langroid.pydantic_v1 import BaseModel
7
-
8
-
9
- class FormattingModel(BaseModel, ABC):
10
- class Config:
11
- arbitrary_types_allowed = True
12
-
13
- START_TOKEN: ClassVar[str] = "<format>"
14
- END_TOKEN: ClassVar[str] = "</format>"
15
-
16
- @classmethod
17
- @abstractmethod
18
- def format_spec(cls) -> str:
19
- pass
20
-
21
- @classmethod
22
- @abstractmethod
23
- def parse_spec(cls) -> str:
24
- pass
25
-
26
- @classmethod
27
- def format(cls, instance: "FormattingModel") -> str:
28
- template = f"{cls.START_TOKEN}\n{{content}}\n{cls.END_TOKEN}"
29
- spec_template = cls.format_spec()
30
- formatted_content = spec_template.format(**instance.dict())
31
- return template.format(content=formatted_content)
32
-
33
- @classmethod
34
- def parse(cls, formatted_string: str) -> "FormattingModel":
35
- outer_template = f"{cls.START_TOKEN}\n{{content}}\n{cls.END_TOKEN}"
36
- outer_parsed = parse(outer_template, formatted_string, case_sensitive=False)
37
- if not outer_parsed:
38
- raise ValueError("Invalid outer format")
39
-
40
- content = outer_parsed["content"]
41
- parse_template = cls.parse_spec()
42
- parsed = parse(parse_template, content, case_sensitive=False)
43
- if not parsed:
44
- raise ValueError(
45
- f"Failed to parse content:\n{content}\nusing spec:\n{parse_template}"
46
- )
47
-
48
- return cls(**parsed.named)
49
-
50
-
51
- class PersonModel(FormattingModel):
52
- name: str
53
- age: int
54
- city: str
55
-
56
- START_TOKEN: ClassVar[str] = "<person>"
57
- END_TOKEN: ClassVar[str] = "</person>"
58
-
59
- @classmethod
60
- def format_spec(cls):
61
- return "name: {name}\n{age} is the age\nlives in {city}"
62
-
63
- @classmethod
64
- def parse_spec(cls):
65
- return "name: {name:S}\n{age:d} is the age\nlives in {city:S}"
66
-
67
-
68
- # Tests
69
- if __name__ == "__main__":
70
- # Test instance
71
- person = PersonModel(name="John Doe", age=30, city="New York")
72
-
73
- # Test formatting
74
- formatted_string = PersonModel.format(person)
75
- print("Formatted string:")
76
- print(formatted_string)
77
- assert formatted_string == (
78
- "<person>\n"
79
- "name: John Doe\n"
80
- "30 is the age\n"
81
- "lives in New York\n"
82
- "</person>"
83
- )
84
-
85
- # Test parsing
86
- parsed_person = PersonModel.parse(formatted_string)
87
- print("\nParsed person:", parsed_person)
88
- assert parsed_person == person
89
-
90
- # Test round trip
91
- round_trip_person = PersonModel.parse(PersonModel.format(person))
92
- assert round_trip_person == person
93
-
94
- # Test parsing with extra whitespace and different casing
95
- extra_whitespace_string = """
96
- <PERSON>
97
- Name: John Doe
98
- 30 IS THE AGE
99
- Lives in New York
100
- </person>
101
- """
102
- parsed_extra_whitespace = PersonModel.parse(extra_whitespace_string)
103
- assert parsed_extra_whitespace == person
104
-
105
- print("All tests passed!")
@@ -1,98 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import ClassVar
3
- from pydantic import BaseModel
4
- from parse import parse
5
-
6
- class FormattingModel(BaseModel, ABC):
7
- class Config:
8
- arbitrary_types_allowed = True
9
-
10
- START_TOKEN: ClassVar[str] = "<format>"
11
- END_TOKEN: ClassVar[str] = "</format>"
12
-
13
- @classmethod
14
- @abstractmethod
15
- def format_spec(cls) -> str:
16
- pass
17
-
18
- @classmethod
19
- @abstractmethod
20
- def parse_spec(cls) -> str:
21
- pass
22
-
23
- @classmethod
24
- def format(cls, instance: 'FormattingModel') -> str:
25
- template = f"{cls.START_TOKEN}\n{{content}}\n{cls.END_TOKEN}"
26
- spec_template = cls.format_spec()
27
- formatted_content = spec_template.format(**instance.dict())
28
- return template.format(content=formatted_content)
29
-
30
- @classmethod
31
- def parse(cls, formatted_string: str) -> 'FormattingModel':
32
- outer_template = f"{cls.START_TOKEN}\n{{content}}\n{cls.END_TOKEN}"
33
- outer_parsed = parse(outer_template, formatted_string, case_sensitive=False)
34
- if not outer_parsed:
35
- raise ValueError("Invalid outer format")
36
-
37
- content = outer_parsed['content']
38
- parse_template = cls.parse_spec()
39
- parsed = parse(parse_template, content, case_sensitive=False)
40
- if not parsed:
41
- raise ValueError(f"Failed to parse content:\n{content}\nusing spec:\n{parse_template}")
42
-
43
- return cls(**parsed.named)
44
-
45
- class PersonModel(FormattingModel):
46
- name: str
47
- age: int
48
- city: str
49
-
50
- START_TOKEN: ClassVar[str] = "<person>"
51
- END_TOKEN: ClassVar[str] = "</person>"
52
-
53
- @classmethod
54
- def format_spec(cls):
55
- return "name: {name}\n{age} is the age\nlives in {city}"
56
-
57
- @classmethod
58
- def parse_spec(cls):
59
- return "name: {name:S}\n{age:d} is the age\nlives in {city:S}"
60
-
61
- # Tests
62
- if __name__ == "__main__":
63
- # Test instance
64
- person = PersonModel(name="John Doe", age=30, city="New York")
65
-
66
- # Test formatting
67
- formatted_string = PersonModel.format(person)
68
- print("Formatted string:")
69
- print(formatted_string)
70
- assert formatted_string == (
71
- "<person>\n"
72
- "name: John Doe\n"
73
- "30 is the age\n"
74
- "lives in New York\n"
75
- "</person>"
76
- )
77
-
78
- # Test parsing
79
- parsed_person = PersonModel.parse(formatted_string)
80
- print("\nParsed person:", parsed_person)
81
- assert parsed_person == person
82
-
83
- # Test round trip
84
- round_trip_person = PersonModel.parse(PersonModel.format(person))
85
- assert round_trip_person == person
86
-
87
- # Test parsing with extra whitespace and different casing
88
- extra_whitespace_string = """
89
- <PERSON>
90
- Name: John Doe
91
- 30 IS THE AGE
92
- Lives in New York
93
- </person>
94
- """
95
- parsed_extra_whitespace = PersonModel.parse(extra_whitespace_string)
96
- assert parsed_extra_whitespace == person
97
-
98
- print("All tests passed!")