langfun 0.1.2.dev202501080804__py3-none-any.whl → 0.1.2.dev202501240804__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 (56) hide show
  1. langfun/core/__init__.py +1 -6
  2. langfun/core/coding/python/__init__.py +5 -11
  3. langfun/core/coding/python/correction.py +4 -7
  4. langfun/core/coding/python/correction_test.py +2 -3
  5. langfun/core/coding/python/execution.py +22 -211
  6. langfun/core/coding/python/execution_test.py +11 -90
  7. langfun/core/coding/python/generation.py +3 -2
  8. langfun/core/coding/python/generation_test.py +2 -2
  9. langfun/core/coding/python/parsing.py +108 -194
  10. langfun/core/coding/python/parsing_test.py +2 -105
  11. langfun/core/component.py +11 -273
  12. langfun/core/component_test.py +2 -29
  13. langfun/core/concurrent.py +187 -82
  14. langfun/core/concurrent_test.py +28 -19
  15. langfun/core/console.py +7 -3
  16. langfun/core/eval/base.py +2 -3
  17. langfun/core/eval/v2/evaluation.py +3 -1
  18. langfun/core/eval/v2/reporting.py +8 -4
  19. langfun/core/language_model.py +84 -8
  20. langfun/core/language_model_test.py +84 -29
  21. langfun/core/llms/__init__.py +46 -11
  22. langfun/core/llms/anthropic.py +1 -123
  23. langfun/core/llms/anthropic_test.py +0 -48
  24. langfun/core/llms/deepseek.py +117 -0
  25. langfun/core/llms/deepseek_test.py +61 -0
  26. langfun/core/llms/gemini.py +1 -1
  27. langfun/core/llms/groq.py +12 -99
  28. langfun/core/llms/groq_test.py +31 -137
  29. langfun/core/llms/llama_cpp.py +17 -54
  30. langfun/core/llms/llama_cpp_test.py +2 -34
  31. langfun/core/llms/openai.py +9 -147
  32. langfun/core/llms/openai_compatible.py +179 -0
  33. langfun/core/llms/openai_compatible_test.py +495 -0
  34. langfun/core/llms/openai_test.py +13 -423
  35. langfun/core/llms/rest_test.py +1 -1
  36. langfun/core/llms/vertexai.py +387 -18
  37. langfun/core/llms/vertexai_test.py +52 -0
  38. langfun/core/message_test.py +3 -3
  39. langfun/core/modalities/mime.py +8 -0
  40. langfun/core/modalities/mime_test.py +19 -4
  41. langfun/core/modality_test.py +0 -1
  42. langfun/core/structured/mapping.py +13 -13
  43. langfun/core/structured/mapping_test.py +2 -2
  44. langfun/core/structured/schema.py +16 -8
  45. langfun/core/structured/schema_generation.py +1 -1
  46. {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/METADATA +13 -2
  47. {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/RECORD +50 -52
  48. {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/WHEEL +1 -1
  49. langfun/core/coding/python/errors.py +0 -108
  50. langfun/core/coding/python/errors_test.py +0 -99
  51. langfun/core/coding/python/permissions.py +0 -90
  52. langfun/core/coding/python/permissions_test.py +0 -86
  53. langfun/core/text_formatting.py +0 -168
  54. langfun/core/text_formatting_test.py +0 -65
  55. {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/LICENSE +0 -0
  56. {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/top_level.txt +0 -0
@@ -1,108 +0,0 @@
1
- # Copyright 2023 The Langfun Authors
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- """Python code errors."""
15
-
16
- import io
17
- import sys
18
- import textwrap
19
- import traceback
20
- import langfun.core as lf
21
-
22
-
23
- class CodeError(RuntimeError):
24
- """Python code error."""
25
-
26
- def __init__(
27
- self,
28
- code: str,
29
- cause: Exception,
30
- ):
31
- self.code = code
32
- self.cause = cause
33
-
34
- # Figure out the starting and ending line numbers of the erratic code.
35
- lineno = None
36
- end_lineno = None
37
- if isinstance(cause, SyntaxError):
38
- lineno = cause.lineno
39
- end_lineno = cause.end_lineno
40
- elif not isinstance(cause, TimeoutError):
41
- tb = sys.exc_info()[2]
42
- frames = traceback.extract_tb(tb, limit=5)
43
- for f in frames:
44
- if not f.filename or f.filename == '<string>':
45
- lineno = f.lineno
46
- end_lineno = lineno
47
- break
48
- self.lineno = lineno
49
- self.end_lineno = end_lineno
50
-
51
- def __str__(self):
52
- return self.format(include_complete_code=True)
53
-
54
- def code_lines(self, start_line: int, end_line: int):
55
- """Returns code lines ."""
56
- return '\n'.join(self.code.split('\n')[start_line:end_line])
57
-
58
- def format(self, include_complete_code: bool = True):
59
- """Formats the code error."""
60
- r = io.StringIO()
61
- error_message = str(self.cause).rstrip()
62
- if 'line' not in error_message and self.lineno is not None:
63
- error_message += f' (<unknown>, line {self.lineno})'
64
- r.write(
65
- lf.colored(
66
- f'{self.cause.__class__.__name__}: {error_message}', 'magenta'))
67
-
68
- if self.lineno is not None:
69
- r.write('\n\n')
70
- r.write(textwrap.indent(
71
- lf.colored(
72
- self.code_lines(self.lineno - 1, self.end_lineno), 'magenta'),
73
- ' ' * 2
74
- ))
75
- r.write('\n')
76
-
77
- if include_complete_code:
78
- r.write('\n')
79
- r.write(lf.colored('[Generated Code]', 'green', styles=['bold']))
80
- r.write('\n\n')
81
- r.write(lf.colored(' ```python\n', 'green'))
82
- r.write(textwrap.indent(
83
- lf.colored(self.code, 'green'),
84
- ' ' * 2
85
- ))
86
- r.write(lf.colored('\n ```\n', 'green'))
87
- return r.getvalue()
88
-
89
-
90
- class SerializationError(RuntimeError):
91
- """Object serialization error."""
92
-
93
- def __init__(self, message: str | None, cause: Exception):
94
- self.message = message
95
- self.cause = cause
96
-
97
- def __str__(self):
98
- r = io.StringIO()
99
- cause_message = str(self.cause).rstrip()
100
- if self.message:
101
- r.write(lf.colored(self.message, 'magenta'))
102
- r.write('\n\n')
103
- r.write(
104
- lf.colored(
105
- f'{self.cause.__class__.__name__}: {cause_message}', 'magenta'
106
- )
107
- )
108
- return r.getvalue()
@@ -1,99 +0,0 @@
1
- # Copyright 2023 The Langfun Authors
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- """Tests for code errors."""
15
-
16
- import unittest
17
-
18
- from langfun.core.coding.python import errors
19
- from langfun.core.coding.python import execution
20
-
21
-
22
- def code_error(code: str) -> errors.CodeError | None:
23
- try:
24
- execution.run(code, timeout=2)
25
- return None
26
- except errors.CodeError as e:
27
- return e
28
-
29
-
30
- class CodeErrorsTest(unittest.TestCase):
31
-
32
- def test_format(self):
33
- e = code_error(
34
- """
35
- x = y + 1
36
- """
37
- )
38
- self.assertIn('[Generated Code]', str(e))
39
- self.assertNotIn(
40
- '[Generated Code]', e.format(include_complete_code=False))
41
-
42
- def test_lineno(self):
43
- self.assertEqual(
44
- code_error(
45
- """
46
- x = y + 1
47
- """
48
- ).lineno, 1)
49
- self.assertEqual(
50
- code_error(
51
- """
52
- x = 1
53
- for i of x:
54
- y = i
55
- """
56
- ).lineno, 2)
57
- self.assertEqual(
58
- code_error(
59
- """
60
- x = 1
61
- y = 2
62
- raise ValueError
63
- """
64
- ).lineno, 3)
65
-
66
- def test_lineno_in_error_message(self):
67
- def assert_lineno(code):
68
- e = code_error(code)
69
- self.assertIn('line', e.format(include_complete_code=False))
70
-
71
- assert_lineno(
72
- """
73
- x = y + 1
74
- """
75
- )
76
- assert_lineno(
77
- """
78
- x = 1
79
- y = 2
80
- """
81
- )
82
- assert_lineno(
83
- """
84
- raise ValueError()
85
- """
86
- )
87
-
88
-
89
- class SerializationErrorTest(unittest.TestCase):
90
-
91
- def test_str(self):
92
- e = errors.SerializationError(
93
- 'Output cannot be serialized.', ValueError('abc'))
94
- self.assertIn('Output cannot be serialized', str(e))
95
- self.assertIn('ValueError: abc', str(e))
96
-
97
-
98
- if __name__ == '__main__':
99
- unittest.main()
@@ -1,90 +0,0 @@
1
- # Copyright 2023 The Langfun Authors
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- """Python code permissions."""
15
-
16
- import contextlib
17
- import enum
18
- import pyglove as pg
19
-
20
-
21
- class CodePermission(enum.Flag):
22
- """Permissions for code execution."""
23
-
24
- # Allows basic Python code: Creating objects, assignment, operations.
25
- BASIC = enum.auto()
26
-
27
- # Allows conditions.
28
- CONDITION = enum.auto()
29
-
30
- # Allows loops.
31
- LOOP = enum.auto()
32
-
33
- # Allows exception.
34
- EXCEPTION = enum.auto()
35
-
36
- # Allows class definitions.
37
- CLASS_DEFINITION = enum.auto()
38
-
39
- # Allows function definitions.
40
- FUNCTION_DEFINITION = enum.auto()
41
-
42
- # Allows import.
43
- IMPORT = enum.auto()
44
-
45
- @classmethod
46
- @property
47
- def ALL(cls) -> 'CodePermission': # pylint: disable=invalid-name
48
- """Returns all permissions."""
49
- return (
50
- CodePermission.BASIC | CodePermission.CONDITION | CodePermission.LOOP |
51
- CodePermission.EXCEPTION | CodePermission.CLASS_DEFINITION |
52
- CodePermission.FUNCTION_DEFINITION | CodePermission.IMPORT)
53
-
54
-
55
- _TLS_CODE_RUN_PERMISSION = '__code_run_permission__'
56
-
57
-
58
- @contextlib.contextmanager
59
- def permission(perm: CodePermission):
60
- """Context manager for controling the permission for code execution.
61
-
62
- When the `permission` context manager is nested, the outtermost permission
63
- will be used. This design allows users to control permission at the top level.
64
-
65
- Args:
66
- perm: Code execution permission.
67
-
68
- Yields:
69
- Actual permission applied.
70
- """
71
-
72
- outter_perm = pg.object_utils.thread_local_get(_TLS_CODE_RUN_PERMISSION, None)
73
-
74
- # Use the top-level permission as the actual permission
75
- if outter_perm is not None:
76
- perm = outter_perm
77
-
78
- pg.object_utils.thread_local_set(_TLS_CODE_RUN_PERMISSION, perm)
79
-
80
- try:
81
- yield perm
82
- finally:
83
- if outter_perm is None:
84
- pg.object_utils.thread_local_del(_TLS_CODE_RUN_PERMISSION)
85
-
86
-
87
- def get_permission() -> CodePermission:
88
- """Gets the current permission for code execution."""
89
- return pg.object_utils.thread_local_get(
90
- _TLS_CODE_RUN_PERMISSION, CodePermission.ALL)
@@ -1,86 +0,0 @@
1
- # Copyright 2023 The Langfun Authors
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- """Tests for Python code permissions."""
15
-
16
- import unittest
17
- from langfun.core.coding.python import permissions
18
-
19
-
20
- class CodePermissionTest(unittest.TestCase):
21
-
22
- def assert_set(
23
- self,
24
- permission: permissions.CodePermission,
25
- flag: permissions.CodePermission,
26
- ):
27
- self.assertEqual(permission & flag, flag)
28
-
29
- def assert_not_set(
30
- self,
31
- permission: permissions.CodePermission,
32
- flag: permissions.CodePermission,
33
- ):
34
- self.assertFalse(permission & flag)
35
-
36
- def test_all(self):
37
- self.assert_set(
38
- permissions.CodePermission.ALL, permissions.CodePermission.BASIC
39
- )
40
- self.assert_set(
41
- permissions.CodePermission.ALL, permissions.CodePermission.CONDITION
42
- )
43
- self.assert_set(
44
- permissions.CodePermission.ALL, permissions.CodePermission.LOOP
45
- )
46
- self.assert_set(
47
- permissions.CodePermission.ALL, permissions.CodePermission.EXCEPTION
48
- )
49
- self.assert_set(
50
- permissions.CodePermission.ALL,
51
- permissions.CodePermission.CLASS_DEFINITION,
52
- )
53
- self.assert_set(
54
- permissions.CodePermission.ALL,
55
- permissions.CodePermission.FUNCTION_DEFINITION,
56
- )
57
- self.assert_set(
58
- permissions.CodePermission.ALL, permissions.CodePermission.IMPORT
59
- )
60
-
61
- def test_xor(self):
62
- self.assert_not_set(
63
- permissions.CodePermission.ALL ^ permissions.CodePermission.BASIC,
64
- permissions.CodePermission.BASIC,
65
- )
66
- self.assert_set(
67
- permissions.CodePermission.ALL ^ permissions.CodePermission.BASIC,
68
- permissions.CodePermission.CONDITION,
69
- )
70
-
71
- def test_permission_control(self):
72
- self.assertEqual(
73
- permissions.get_permission(), permissions.CodePermission.ALL
74
- )
75
- with permissions.permission(permissions.CodePermission.BASIC):
76
- self.assertEqual(
77
- permissions.get_permission(), permissions.CodePermission.BASIC
78
- )
79
- with permissions.permission(permissions.CodePermission.ALL):
80
- self.assertEqual(
81
- permissions.get_permission(), permissions.CodePermission.BASIC
82
- )
83
-
84
-
85
- if __name__ == '__main__':
86
- unittest.main()
@@ -1,168 +0,0 @@
1
- # Copyright 2023 The Langfun Authors
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- """Utility library for LM input/output formatting."""
15
-
16
- import io
17
- import re
18
- from typing import Any
19
-
20
- try:
21
- import termcolor # pylint: disable=g-import-not-at-top
22
- except ImportError:
23
- termcolor = None
24
-
25
-
26
- # Regular expression for ANSI color characters.
27
- _ANSI_COLOR_REGEX = re.compile(r'\x1b\[[0-9;]*m')
28
-
29
-
30
- def decolored(text: str) -> str:
31
- """Return the de-colored string that may contains ANSI color characters."""
32
- return re.sub(_ANSI_COLOR_REGEX, '', text)
33
-
34
-
35
- def colored(
36
- text: str,
37
- color: str | None = None,
38
- background: str | None = None,
39
- styles: list[str] | None = None
40
- ) -> str:
41
- """Returns the colored text with ANSI color characters.
42
-
43
- Args:
44
- text: A string that may or may not already has ANSI color characters.
45
- color: A string for text colors. Applicable values are:
46
- 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
47
- background: A string for background colors. Applicable values are:
48
- 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
49
- styles: A list of strings for applying styles on the text.
50
- Applicable values are:
51
- 'bold', 'dark', 'underline', 'blink', 'reverse', 'concealed'.
52
-
53
- Returns:
54
- A string with ANSI color characters embracing the entire text.
55
- """
56
- if not termcolor:
57
- return text
58
- return termcolor.colored(
59
- text,
60
- color=color,
61
- on_color=('on_' + background) if background else None,
62
- attrs=styles)
63
-
64
-
65
- def colored_template(
66
- text: str,
67
- expression_color: str | None = 'white',
68
- expression_background: str | None = 'blue',
69
- expression_styles: list[str] | None = None,
70
- statement_color: str | None = 'red',
71
- statement_background: str | None = None,
72
- statement_styles: list[str] | None = None,
73
- comment_color: str | None = 'green',
74
- comment_background: str | None = None,
75
- comment_styles: list[str] | None = None,
76
- ) -> str:
77
- """Returns colored (maybe) Jinja2 template string."""
78
- text = color_text_blocks(
79
- text, '{{', '}}',
80
- color=expression_color,
81
- background=expression_background,
82
- styles=expression_styles)
83
-
84
- text = color_text_blocks(
85
- text, '{%', '%}',
86
- color=statement_color,
87
- background=statement_background,
88
- styles=statement_styles)
89
-
90
- text = color_text_blocks(
91
- text, '{#', '#}',
92
- color=comment_color,
93
- background=comment_background,
94
- styles=comment_styles)
95
-
96
- return text
97
-
98
-
99
- def color_text_blocks(
100
- text: str,
101
- block_start: str,
102
- block_end: str,
103
- color: str | None = None,
104
- background: str | None = None,
105
- styles: list[str] | None = None
106
- ) -> str:
107
- """Apply colors to text blocks.
108
-
109
- Args:
110
- text: A string that may or may not already has ANSI color characters.
111
- block_start: A string that signals the start of a block. E.g. '{{'
112
- block_end: A string that signals the end of a block. E.g. '}}'.
113
- color: A string for text colors. Applicable values are:
114
- 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
115
- background: A string for background colors. Applicable values are:
116
- 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
117
- styles: A list of strings for applying styles on the text.
118
- Applicable values are:
119
- 'bold', 'dark', 'underline', 'blink', 'reverse', 'concealed'.
120
-
121
- Returns:
122
- A string with ANSI color characters embracing the matched text blocks.
123
- """
124
- if not color and not background and not styles:
125
- return text
126
-
127
- string_buffer = io.StringIO()
128
- start_index = 0
129
- end_index = 0
130
- previous_color = None
131
-
132
- def write_nonblock_text(text: str, previous_color: str | None):
133
- if previous_color:
134
- string_buffer.write(previous_color)
135
- string_buffer.write(text)
136
-
137
- while start_index < len(text):
138
- start_index = text.find(block_start, end_index)
139
- if start_index == -1:
140
- write_nonblock_text(text[end_index:], previous_color)
141
- break
142
-
143
- # Deal with text since last block.
144
- since_last_block = text[end_index:start_index]
145
- write_nonblock_text(since_last_block, previous_color)
146
- colors = re.findall(_ANSI_COLOR_REGEX, since_last_block)
147
- if colors:
148
- previous_color = colors[-1]
149
-
150
- # Match block.
151
- end_index = text.find(block_end, start_index + len(block_start))
152
- if end_index == -1:
153
- write_nonblock_text(text[start_index:], previous_color)
154
- break
155
- end_index += len(block_end)
156
-
157
- # Write block text.
158
- block = text[start_index:end_index]
159
- colored_block = colored(
160
- block, color=color, background=background, styles=styles)
161
- string_buffer.write(colored_block)
162
- return string_buffer.getvalue()
163
-
164
-
165
- def colored_print(value: Any):
166
- """Prints text with color."""
167
- print(colored_template(str(value)))
168
-
@@ -1,65 +0,0 @@
1
- # Copyright 2023 The Langfun Authors
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- """Tests for text formatting."""
15
-
16
- import inspect
17
- import unittest
18
- from langfun.core import text_formatting
19
-
20
-
21
- class TextFormattingTest(unittest.TestCase):
22
-
23
- def test_colored_template(self):
24
- original_text = inspect.cleandoc("""
25
- Hi {{ foo }}
26
- {# print x if x is present #}
27
- {% if x %}
28
- {{ x }}
29
- {% endif %}
30
- """)
31
-
32
- colored_text = text_formatting.colored_template(
33
- text_formatting.colored(original_text, color='blue')
34
- )
35
- self.assertEqual(
36
- colored_text,
37
- '\x1b[34mHi \x1b[44m\x1b[37m{{ foo }}\x1b[0m\x1b[34m\n'
38
- '\x1b[32m{# print x if x is present #}\x1b[0m\x1b[34m\n'
39
- '\x1b[31m{% if x %}\x1b[0m\x1b[34m\n'
40
- '\x1b[44m\x1b[37m{{ x }}\x1b[0m\x1b[34m\n'
41
- '\x1b[31m{% endif %}\x1b[0m\x1b[34m\x1b[0m'
42
- )
43
- self.assertEqual(text_formatting.decolored(colored_text), original_text)
44
-
45
- def test_colored_without_termcolor(self):
46
- termcolor = text_formatting.termcolor
47
- text_formatting.termcolor = None
48
- original_text = inspect.cleandoc("""
49
- Hi {{ foo }}
50
- {# print x if x is present #}
51
- {% if x %}
52
- {{ x }}
53
- {% endif %}
54
- """)
55
-
56
- colored_text = text_formatting.colored_template(
57
- text_formatting.colored(original_text, color='blue')
58
- )
59
- self.assertEqual(colored_text, original_text)
60
- self.assertEqual(text_formatting.decolored(colored_text), original_text)
61
- text_formatting.termcolor = termcolor
62
-
63
-
64
- if __name__ == '__main__':
65
- unittest.main()