langfun 0.0.2.dev20240429__py3-none-any.whl → 0.1.2.dev202501140804__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.
- langfun/__init__.py +20 -2
- langfun/core/__init__.py +16 -5
- langfun/core/agentic/__init__.py +30 -0
- langfun/core/agentic/action.py +854 -0
- langfun/core/agentic/action_eval.py +150 -0
- langfun/core/agentic/action_eval_test.py +109 -0
- langfun/core/agentic/action_test.py +136 -0
- langfun/core/coding/python/__init__.py +5 -11
- langfun/core/coding/python/correction.py +37 -21
- langfun/core/coding/python/correction_test.py +29 -3
- langfun/core/coding/python/execution.py +40 -216
- langfun/core/coding/python/execution_test.py +29 -89
- langfun/core/coding/python/generation.py +21 -11
- langfun/core/coding/python/generation_test.py +2 -2
- langfun/core/coding/python/parsing.py +108 -193
- langfun/core/coding/python/parsing_test.py +2 -105
- langfun/core/component.py +63 -2
- langfun/core/component_test.py +53 -0
- langfun/core/concurrent.py +414 -117
- langfun/core/concurrent_test.py +111 -24
- langfun/core/console.py +18 -5
- langfun/core/console_test.py +17 -0
- langfun/core/eval/__init__.py +16 -1
- langfun/core/eval/base.py +622 -174
- langfun/core/eval/base_test.py +200 -54
- langfun/core/eval/matching.py +63 -76
- langfun/core/eval/matching_test.py +17 -8
- langfun/core/eval/patching.py +130 -0
- langfun/core/eval/patching_test.py +170 -0
- langfun/core/eval/scoring.py +26 -26
- langfun/core/eval/scoring_test.py +19 -2
- langfun/core/eval/v2/__init__.py +42 -0
- langfun/core/eval/v2/checkpointing.py +380 -0
- langfun/core/eval/v2/checkpointing_test.py +228 -0
- langfun/core/eval/v2/eval_test_helper.py +136 -0
- langfun/core/eval/v2/evaluation.py +725 -0
- langfun/core/eval/v2/evaluation_test.py +180 -0
- langfun/core/eval/v2/example.py +305 -0
- langfun/core/eval/v2/example_test.py +128 -0
- langfun/core/eval/v2/experiment.py +1048 -0
- langfun/core/eval/v2/experiment_test.py +433 -0
- langfun/core/eval/v2/metric_values.py +156 -0
- langfun/core/eval/v2/metric_values_test.py +80 -0
- langfun/core/eval/v2/metrics.py +357 -0
- langfun/core/eval/v2/metrics_test.py +203 -0
- langfun/core/eval/v2/progress.py +348 -0
- langfun/core/eval/v2/progress_test.py +82 -0
- langfun/core/eval/v2/progress_tracking.py +210 -0
- langfun/core/eval/v2/progress_tracking_test.py +66 -0
- langfun/core/eval/v2/reporting.py +270 -0
- langfun/core/eval/v2/reporting_test.py +158 -0
- langfun/core/eval/v2/runners.py +488 -0
- langfun/core/eval/v2/runners_test.py +334 -0
- langfun/core/langfunc.py +4 -17
- langfun/core/langfunc_test.py +22 -6
- langfun/core/language_model.py +577 -39
- langfun/core/language_model_test.py +470 -56
- langfun/core/llms/__init__.py +87 -16
- langfun/core/llms/anthropic.py +312 -87
- langfun/core/llms/anthropic_test.py +71 -3
- langfun/core/llms/cache/base.py +21 -2
- langfun/core/llms/cache/in_memory.py +13 -0
- langfun/core/llms/cache/in_memory_test.py +53 -2
- langfun/core/llms/compositional.py +101 -0
- langfun/core/llms/compositional_test.py +73 -0
- langfun/core/llms/deepseek.py +117 -0
- langfun/core/llms/deepseek_test.py +61 -0
- langfun/core/llms/fake.py +11 -7
- langfun/core/llms/fake_test.py +14 -0
- langfun/core/llms/gemini.py +507 -0
- langfun/core/llms/gemini_test.py +195 -0
- langfun/core/llms/google_genai.py +62 -218
- langfun/core/llms/google_genai_test.py +9 -202
- langfun/core/llms/groq.py +160 -144
- langfun/core/llms/groq_test.py +31 -137
- langfun/core/llms/llama_cpp.py +15 -42
- langfun/core/llms/llama_cpp_test.py +4 -30
- langfun/core/llms/openai.py +395 -203
- langfun/core/llms/openai_compatible.py +179 -0
- langfun/core/llms/openai_compatible_test.py +495 -0
- langfun/core/llms/openai_test.py +30 -395
- langfun/core/llms/rest.py +113 -0
- langfun/core/llms/rest_test.py +111 -0
- langfun/core/llms/vertexai.py +192 -0
- langfun/core/llms/vertexai_test.py +52 -0
- langfun/core/logging.py +284 -0
- langfun/core/logging_test.py +125 -0
- langfun/core/message.py +319 -9
- langfun/core/message_test.py +190 -13
- langfun/core/modalities/__init__.py +6 -2
- langfun/core/modalities/audio.py +30 -0
- langfun/core/modalities/audio_test.py +63 -0
- langfun/core/modalities/image.py +39 -20
- langfun/core/modalities/image_test.py +52 -9
- langfun/core/modalities/mime.py +206 -29
- langfun/core/modalities/mime_test.py +90 -9
- langfun/core/modalities/ms_office.py +117 -0
- langfun/core/modalities/ms_office_test.py +389 -0
- langfun/core/modalities/pdf.py +22 -0
- langfun/core/modalities/pdf_test.py +57 -0
- langfun/core/modalities/video.py +9 -26
- langfun/core/modalities/video_test.py +3 -3
- langfun/core/modality.py +26 -3
- langfun/core/modality_test.py +2 -2
- langfun/core/sampling.py +11 -11
- langfun/core/structured/__init__.py +12 -16
- langfun/core/structured/completion.py +32 -5
- langfun/core/structured/completion_test.py +7 -6
- langfun/core/structured/description.py +2 -2
- langfun/core/structured/description_test.py +3 -3
- langfun/core/structured/function_generation.py +60 -27
- langfun/core/structured/function_generation_test.py +72 -2
- langfun/core/structured/mapping.py +97 -47
- langfun/core/structured/mapping_test.py +90 -2
- langfun/core/structured/parsing.py +33 -21
- langfun/core/structured/parsing_test.py +53 -9
- langfun/core/structured/querying.py +746 -0
- langfun/core/structured/{prompting_test.py → querying_test.py} +469 -51
- langfun/core/structured/schema.py +204 -97
- langfun/core/structured/schema_generation.py +1 -1
- langfun/core/structured/schema_test.py +130 -29
- langfun/core/structured/scoring.py +125 -19
- langfun/core/structured/scoring_test.py +30 -0
- langfun/core/structured/tokenization.py +64 -0
- langfun/core/structured/tokenization_test.py +48 -0
- langfun/core/template.py +115 -1
- langfun/core/template_test.py +71 -1
- langfun/core/templates/conversation.py +9 -0
- langfun/core/templates/conversation_test.py +4 -3
- langfun/core/templates/selfplay_test.py +10 -2
- langfun-0.1.2.dev202501140804.dist-info/METADATA +225 -0
- langfun-0.1.2.dev202501140804.dist-info/RECORD +153 -0
- {langfun-0.0.2.dev20240429.dist-info → langfun-0.1.2.dev202501140804.dist-info}/WHEEL +1 -1
- langfun/core/coding/python/errors.py +0 -108
- langfun/core/coding/python/errors_test.py +0 -99
- langfun/core/coding/python/permissions.py +0 -90
- langfun/core/coding/python/permissions_test.py +0 -86
- langfun/core/structured/prompting.py +0 -238
- langfun/core/text_formatting.py +0 -162
- langfun/core/text_formatting_test.py +0 -47
- langfun-0.0.2.dev20240429.dist-info/METADATA +0 -100
- langfun-0.0.2.dev20240429.dist-info/RECORD +0 -108
- {langfun-0.0.2.dev20240429.dist-info → langfun-0.1.2.dev202501140804.dist-info}/LICENSE +0 -0
- {langfun-0.0.2.dev20240429.dist-info → langfun-0.1.2.dev202501140804.dist-info}/top_level.txt +0 -0
langfun/core/message_test.py
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
|
16
16
|
import inspect
|
17
17
|
import unittest
|
18
|
+
from langfun.core import language_model
|
18
19
|
from langfun.core import message
|
19
20
|
from langfun.core import modality
|
20
21
|
import pyglove as pg
|
@@ -36,11 +37,19 @@ class MessageTest(unittest.TestCase):
|
|
36
37
|
|
37
38
|
d = pg.Dict(x=A())
|
38
39
|
|
39
|
-
m = message.UserMessage(
|
40
|
+
m = message.UserMessage(
|
41
|
+
'hi',
|
42
|
+
metadata=dict(x=1), x=pg.Ref(d.x),
|
43
|
+
y=2,
|
44
|
+
tags=['lm-input']
|
45
|
+
)
|
40
46
|
self.assertEqual(m.metadata, {'x': pg.Ref(d.x), 'y': 2})
|
41
47
|
self.assertEqual(m.sender, 'User')
|
42
48
|
self.assertIs(m.x, d.x)
|
43
49
|
self.assertEqual(m.y, 2)
|
50
|
+
self.assertTrue(m.has_tag('lm-input'))
|
51
|
+
self.assertTrue(m.has_tag(('lm-input', '')))
|
52
|
+
self.assertFalse(m.has_tag('lm-response'))
|
44
53
|
|
45
54
|
with self.assertRaises(AttributeError):
|
46
55
|
_ = m.z
|
@@ -54,7 +63,7 @@ class MessageTest(unittest.TestCase):
|
|
54
63
|
self.assertTrue(
|
55
64
|
pg.eq(
|
56
65
|
message.UserMessage.from_value(CustomModality('foo')),
|
57
|
-
message.UserMessage('
|
66
|
+
message.UserMessage('<<[[object]]>>', object=CustomModality('foo')),
|
58
67
|
)
|
59
68
|
)
|
60
69
|
m = message.UserMessage('hi')
|
@@ -258,13 +267,16 @@ class MessageTest(unittest.TestCase):
|
|
258
267
|
|
259
268
|
def test_referred_modalities(self):
|
260
269
|
m1 = message.UserMessage(
|
261
|
-
'hi, this is a
|
270
|
+
'hi, this is a <<[[img1]]>> and <<[[x.img2]]>>',
|
262
271
|
img1=CustomModality('foo'),
|
263
272
|
x=dict(img2=CustomModality('bar')),
|
264
273
|
)
|
265
274
|
m2 = message.SystemMessage('class Question:\n image={{img1}}', source=m1)
|
266
275
|
m3 = message.AIMessage(
|
267
|
-
|
276
|
+
(
|
277
|
+
'This is the <<[[output_image]]>> based on <<[[x.img2]]>>, '
|
278
|
+
'{{unknown_var}}'
|
279
|
+
),
|
268
280
|
output_image=CustomModality('bar'),
|
269
281
|
source=m2,
|
270
282
|
)
|
@@ -276,11 +288,25 @@ class MessageTest(unittest.TestCase):
|
|
276
288
|
},
|
277
289
|
)
|
278
290
|
|
291
|
+
def test_text_with_modality_hash(self):
|
292
|
+
m = message.UserMessage(
|
293
|
+
'hi, this is a <<[[img1]]>> and <<[[x.img2]]>>',
|
294
|
+
img1=CustomModality('foo'),
|
295
|
+
x=dict(img2=CustomModality('bar')),
|
296
|
+
)
|
297
|
+
self.assertEqual(
|
298
|
+
m.text_with_modality_hash,
|
299
|
+
(
|
300
|
+
'hi, this is a <<[[img1]]>> and <<[[x.img2]]>>'
|
301
|
+
'<img1>acbd18db</img1><x.img2>37b51d19</x.img2>'
|
302
|
+
)
|
303
|
+
)
|
304
|
+
|
279
305
|
def test_chunking(self):
|
280
306
|
m = message.UserMessage(
|
281
307
|
inspect.cleandoc("""
|
282
|
-
Hi, this is
|
283
|
-
|
308
|
+
Hi, this is <<[[a]]>> and this is {{b}}.
|
309
|
+
<<[[x.c]]>> {{something else
|
284
310
|
"""),
|
285
311
|
a=CustomModality('foo'),
|
286
312
|
x=dict(c=CustomModality('bar')),
|
@@ -292,9 +318,9 @@ class MessageTest(unittest.TestCase):
|
|
292
318
|
[
|
293
319
|
'Hi, this is',
|
294
320
|
CustomModality('foo'),
|
295
|
-
'and this is {{b}}
|
321
|
+
'and this is {{b}}.\n',
|
296
322
|
CustomModality('bar'),
|
297
|
-
'
|
323
|
+
'{{something else',
|
298
324
|
],
|
299
325
|
)
|
300
326
|
)
|
@@ -303,11 +329,8 @@ class MessageTest(unittest.TestCase):
|
|
303
329
|
message.AIMessage.from_chunks(chunks),
|
304
330
|
message.AIMessage(
|
305
331
|
inspect.cleandoc("""
|
306
|
-
Hi, this is
|
307
|
-
{{
|
308
|
-
and this is {{b}}.
|
309
|
-
{{obj1}}
|
310
|
-
{{something else
|
332
|
+
Hi, this is <<[[obj0]]>> and this is {{b}}.
|
333
|
+
<<[[obj1]]>> {{something else
|
311
334
|
"""),
|
312
335
|
obj0=pg.Ref(m.a),
|
313
336
|
obj1=pg.Ref(m.x.c),
|
@@ -315,6 +338,160 @@ class MessageTest(unittest.TestCase):
|
|
315
338
|
)
|
316
339
|
)
|
317
340
|
|
341
|
+
def assert_html_content(self, html, expected):
|
342
|
+
expected = inspect.cleandoc(expected).strip()
|
343
|
+
actual = html.content.strip()
|
344
|
+
if actual != expected:
|
345
|
+
print(actual)
|
346
|
+
self.assertEqual(actual, expected)
|
347
|
+
|
348
|
+
def test_html_style(self):
|
349
|
+
self.assertIn(
|
350
|
+
inspect.cleandoc(
|
351
|
+
"""
|
352
|
+
/* Langfun Message styles.*/
|
353
|
+
[class^="message-"] > details {
|
354
|
+
margin: 0px 0px 5px 0px;
|
355
|
+
border: 1px solid #EEE;
|
356
|
+
}
|
357
|
+
.lf-message.summary-title::after {
|
358
|
+
content: ' 💬';
|
359
|
+
}
|
360
|
+
details.pyglove.ai-message {
|
361
|
+
border: 1px solid blue;
|
362
|
+
color: blue;
|
363
|
+
}
|
364
|
+
details.pyglove.user-message {
|
365
|
+
border: 1px solid green;
|
366
|
+
color: green;
|
367
|
+
}
|
368
|
+
.message-tags {
|
369
|
+
margin: 5px 0px 5px 0px;
|
370
|
+
font-size: .8em;
|
371
|
+
}
|
372
|
+
.message-tags > span {
|
373
|
+
border-radius: 5px;
|
374
|
+
background-color: #CCC;
|
375
|
+
padding: 3px;
|
376
|
+
margin: 0px 2px 0px 2px;
|
377
|
+
color: white;
|
378
|
+
}
|
379
|
+
.message-text {
|
380
|
+
padding: 20px;
|
381
|
+
margin: 10px 5px 10px 5px;
|
382
|
+
font-style: italic;
|
383
|
+
white-space: pre-wrap;
|
384
|
+
border: 1px solid #EEE;
|
385
|
+
border-radius: 5px;
|
386
|
+
background-color: #EEE;
|
387
|
+
}
|
388
|
+
.modality-in-text {
|
389
|
+
display: inline-block;
|
390
|
+
}
|
391
|
+
.modality-in-text > details.pyglove {
|
392
|
+
display: inline-block;
|
393
|
+
font-size: 0.8em;
|
394
|
+
border: 0;
|
395
|
+
background-color: #A6F1A6;
|
396
|
+
margin: 0px 5px 0px 5px;
|
397
|
+
}
|
398
|
+
.message-result {
|
399
|
+
color: dodgerblue;
|
400
|
+
}
|
401
|
+
.message-usage {
|
402
|
+
color: orange;
|
403
|
+
}
|
404
|
+
.message-usage .object-key.str {
|
405
|
+
border: 1px solid orange;
|
406
|
+
background-color: orange;
|
407
|
+
color: white;
|
408
|
+
}
|
409
|
+
"""
|
410
|
+
),
|
411
|
+
message.UserMessage('hi').to_html().style_section,
|
412
|
+
)
|
413
|
+
|
414
|
+
def test_html_user_message(self):
|
415
|
+
self.assert_html_content(
|
416
|
+
message.UserMessage(
|
417
|
+
'what is a <div>'
|
418
|
+
).to_html(enable_summary_tooltip=False),
|
419
|
+
"""
|
420
|
+
<details open class="pyglove user-message lf-message"><summary><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"></div><div class="message-text">what is a <div></div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div></div></details>
|
421
|
+
"""
|
422
|
+
)
|
423
|
+
self.assert_html_content(
|
424
|
+
message.UserMessage(
|
425
|
+
'what is this <<[[image]]>>',
|
426
|
+
tags=['lm-input'],
|
427
|
+
image=CustomModality('bird')
|
428
|
+
).to_html(
|
429
|
+
enable_summary_tooltip=False,
|
430
|
+
extra_flags=dict(include_message_metadata=False)
|
431
|
+
),
|
432
|
+
"""
|
433
|
+
<details open class="pyglove user-message lf-message"><summary><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">what is this<div class="modality-in-text"><details class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><details open class="pyglove str"><summary><div class="summary-name">content<span class="tooltip">metadata.image.content</span></div><div class="summary-title">str</div></summary><span class="simple-value str">'bird'</span></details></div></details></div></div></div></details>
|
434
|
+
"""
|
435
|
+
)
|
436
|
+
|
437
|
+
def test_html_ai_message(self):
|
438
|
+
image = CustomModality('foo')
|
439
|
+
user_message = message.UserMessage(
|
440
|
+
'What is in this image? <<[[image]]>> this is a test',
|
441
|
+
metadata=dict(image=image),
|
442
|
+
source=message.UserMessage('User input'),
|
443
|
+
tags=['lm-input']
|
444
|
+
)
|
445
|
+
ai_message = message.AIMessage(
|
446
|
+
'My name is Gemini',
|
447
|
+
metadata=dict(
|
448
|
+
result=pg.Dict(x=1, y=2, z=pg.Dict(a=[12, 323])),
|
449
|
+
usage=language_model.LMSamplingUsage(10, 2, 12)
|
450
|
+
),
|
451
|
+
tags=['lm-response', 'lm-output'],
|
452
|
+
source=user_message,
|
453
|
+
)
|
454
|
+
self.assert_html_content(
|
455
|
+
ai_message.to_html(enable_summary_tooltip=False),
|
456
|
+
"""
|
457
|
+
<details open class="pyglove ai-message lf-message"><summary><div class="summary-title lf-message">AIMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-response</span><span>lm-output</span></div><div class="message-text">My name is Gemini</div><div class="message-result"><details open class="pyglove dict"><summary><div class="summary-name">result<span class="tooltip">metadata.result</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">x<span class="tooltip">metadata.result.x</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove int"><summary><div class="summary-name">y<span class="tooltip">metadata.result.y</span></div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details><details class="pyglove dict"><summary><div class="summary-name">z<span class="tooltip">metadata.result.z</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details class="pyglove list"><summary><div class="summary-name">a<span class="tooltip">metadata.result.z.a</span></div><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">metadata.result.z.a[0]</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">metadata.result.z.a[1]</span></td><td><span class="simple-value int">323</span></td></tr></table></div></details></div></details></div></details></div><div class="message-usage"><details open class="pyglove lm-sampling-usage"><summary><div class="summary-name">llm usage<span class="tooltip">metadata.usage</span></div><div class="summary-title">LMSamplingUsage(...)</div></summary><div class="complex-value lm-sampling-usage"><table><tr><td><span class="object-key str">prompt_tokens</span><span class="tooltip">metadata.usage.prompt_tokens</span></td><td><span class="simple-value int">10</span></td></tr><tr><td><span class="object-key str">completion_tokens</span><span class="tooltip">metadata.usage.completion_tokens</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">total_tokens</span><span class="tooltip">metadata.usage.total_tokens</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key str">num_requests</span><span class="tooltip">metadata.usage.num_requests</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">estimated_cost</span><span class="tooltip">metadata.usage.estimated_cost</span></td><td><span class="simple-value none-type">None</span></td></tr><tr><td><span class="object-key str">retry_stats</span><span class="tooltip">metadata.usage.retry_stats</span></td><td><details class="pyglove retry-stats"><summary><div class="summary-title">RetryStats(...)</div></summary><div class="complex-value retry-stats"><table><tr><td><span class="object-key str">num_occurences</span><span class="tooltip">metadata.usage.retry_stats.num_occurences</span></td><td><span class="simple-value int">0</span></td></tr><tr><td><span class="object-key str">total_wait_interval</span><span class="tooltip">metadata.usage.retry_stats.total_wait_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">total_call_interval</span><span class="tooltip">metadata.usage.retry_stats.total_call_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">errors</span><span class="tooltip">metadata.usage.retry_stats.errors</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></td></tr></table></div></details></td></tr></table></div></details></div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details open class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">What is in this image?<div class="modality-in-text"><details class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">source.metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><details open class="pyglove str"><summary><div class="summary-name">content<span class="tooltip">source.metadata.image.content</span></div><div class="summary-title">str</div></summary><span class="simple-value str">'foo'</span></details></div></details></div>this is a test</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><details class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">source.metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><details open class="pyglove str"><summary><div class="summary-name">content<span class="tooltip">source.metadata.image.content</span></div><div class="summary-title">str</div></summary><span class="simple-value str">'foo'</span></details></div></details></div></details></div></div></details></div></details>
|
458
|
+
"""
|
459
|
+
)
|
460
|
+
self.assert_html_content(
|
461
|
+
ai_message.to_html(
|
462
|
+
key_style='label',
|
463
|
+
enable_summary_tooltip=False,
|
464
|
+
extra_flags=dict(
|
465
|
+
collapse_modalities_in_text=False,
|
466
|
+
collapse_llm_usage=True,
|
467
|
+
collapse_message_result_level=0,
|
468
|
+
collapse_message_metadata_level=0,
|
469
|
+
collapse_source_message_level=0,
|
470
|
+
source_tag=None,
|
471
|
+
),
|
472
|
+
),
|
473
|
+
"""
|
474
|
+
<details open class="pyglove ai-message lf-message"><summary><div class="summary-title lf-message">AIMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-response</span><span>lm-output</span></div><div class="message-text">My name is Gemini</div><div class="message-result"><details class="pyglove dict"><summary><div class="summary-name">result<span class="tooltip">metadata.result</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">x</span><span class="tooltip">metadata.result.x</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">metadata.result.y</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">z</span><span class="tooltip">metadata.result.z</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">a</span><span class="tooltip">metadata.result.z.a</span></td><td><details class="pyglove list"><summary><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">metadata.result.z.a[0]</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">metadata.result.z.a[1]</span></td><td><span class="simple-value int">323</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></div><div class="message-usage"><details class="pyglove lm-sampling-usage"><summary><div class="summary-name">llm usage<span class="tooltip">metadata.usage</span></div><div class="summary-title">LMSamplingUsage(...)</div></summary><div class="complex-value lm-sampling-usage"><table><tr><td><span class="object-key str">prompt_tokens</span><span class="tooltip">metadata.usage.prompt_tokens</span></td><td><span class="simple-value int">10</span></td></tr><tr><td><span class="object-key str">completion_tokens</span><span class="tooltip">metadata.usage.completion_tokens</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">total_tokens</span><span class="tooltip">metadata.usage.total_tokens</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key str">num_requests</span><span class="tooltip">metadata.usage.num_requests</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">estimated_cost</span><span class="tooltip">metadata.usage.estimated_cost</span></td><td><span class="simple-value none-type">None</span></td></tr><tr><td><span class="object-key str">retry_stats</span><span class="tooltip">metadata.usage.retry_stats</span></td><td><details class="pyglove retry-stats"><summary><div class="summary-title">RetryStats(...)</div></summary><div class="complex-value retry-stats"><table><tr><td><span class="object-key str">num_occurences</span><span class="tooltip">metadata.usage.retry_stats.num_occurences</span></td><td><span class="simple-value int">0</span></td></tr><tr><td><span class="object-key str">total_wait_interval</span><span class="tooltip">metadata.usage.retry_stats.total_wait_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">total_call_interval</span><span class="tooltip">metadata.usage.retry_stats.total_call_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">errors</span><span class="tooltip">metadata.usage.retry_stats.errors</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></td></tr></table></div></details></td></tr></table></div></details></div><div class="message-metadata"><details class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">What is in this image?<div class="modality-in-text"><details open class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">source.metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">source.metadata.image.content</span></td><td><span class="simple-value str">'foo'</span></td></tr></table></div></details></div>this is a test</div><div class="message-metadata"><details class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">image</span><span class="tooltip">source.metadata.image</span></td><td><details class="pyglove custom-modality"><summary><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">source.metadata.image.content</span></td><td><span class="simple-value str">'foo'</span></td></tr></table></div></details></td></tr></table></div></details></div><details class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source.source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"></div><div class="message-text">User input</div><div class="message-metadata"><details class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div></div></details></div></details></div></details>
|
475
|
+
"""
|
476
|
+
)
|
477
|
+
self.assert_html_content(
|
478
|
+
ai_message.to_html(
|
479
|
+
key_style='label',
|
480
|
+
enable_summary_tooltip=False,
|
481
|
+
extra_flags=dict(
|
482
|
+
collapse_modalities_in_text=True,
|
483
|
+
collapse_llm_usage=False,
|
484
|
+
collapse_message_result_level=1,
|
485
|
+
collapse_message_metadata_level=1,
|
486
|
+
collapse_source_message_level=2,
|
487
|
+
source_tag=None,
|
488
|
+
),
|
489
|
+
),
|
490
|
+
"""
|
491
|
+
<details open class="pyglove ai-message lf-message"><summary><div class="summary-title lf-message">AIMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-response</span><span>lm-output</span></div><div class="message-text">My name is Gemini</div><div class="message-result"><details open class="pyglove dict"><summary><div class="summary-name">result<span class="tooltip">metadata.result</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">x</span><span class="tooltip">metadata.result.x</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">metadata.result.y</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">z</span><span class="tooltip">metadata.result.z</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">a</span><span class="tooltip">metadata.result.z.a</span></td><td><details class="pyglove list"><summary><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">metadata.result.z.a[0]</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">metadata.result.z.a[1]</span></td><td><span class="simple-value int">323</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></div><div class="message-usage"><details open class="pyglove lm-sampling-usage"><summary><div class="summary-name">llm usage<span class="tooltip">metadata.usage</span></div><div class="summary-title">LMSamplingUsage(...)</div></summary><div class="complex-value lm-sampling-usage"><table><tr><td><span class="object-key str">prompt_tokens</span><span class="tooltip">metadata.usage.prompt_tokens</span></td><td><span class="simple-value int">10</span></td></tr><tr><td><span class="object-key str">completion_tokens</span><span class="tooltip">metadata.usage.completion_tokens</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">total_tokens</span><span class="tooltip">metadata.usage.total_tokens</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key str">num_requests</span><span class="tooltip">metadata.usage.num_requests</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">estimated_cost</span><span class="tooltip">metadata.usage.estimated_cost</span></td><td><span class="simple-value none-type">None</span></td></tr><tr><td><span class="object-key str">retry_stats</span><span class="tooltip">metadata.usage.retry_stats</span></td><td><details class="pyglove retry-stats"><summary><div class="summary-title">RetryStats(...)</div></summary><div class="complex-value retry-stats"><table><tr><td><span class="object-key str">num_occurences</span><span class="tooltip">metadata.usage.retry_stats.num_occurences</span></td><td><span class="simple-value int">0</span></td></tr><tr><td><span class="object-key str">total_wait_interval</span><span class="tooltip">metadata.usage.retry_stats.total_wait_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">total_call_interval</span><span class="tooltip">metadata.usage.retry_stats.total_call_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">errors</span><span class="tooltip">metadata.usage.retry_stats.errors</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></td></tr></table></div></details></td></tr></table></div></details></div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details open class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">What is in this image?<div class="modality-in-text"><details class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">source.metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">source.metadata.image.content</span></td><td><span class="simple-value str">'foo'</span></td></tr></table></div></details></div>this is a test</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">image</span><span class="tooltip">source.metadata.image</span></td><td><details class="pyglove custom-modality"><summary><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">source.metadata.image.content</span></td><td><span class="simple-value str">'foo'</span></td></tr></table></div></details></td></tr></table></div></details></div><details open class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source.source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"></div><div class="message-text">User input</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div></div></details></div></details></div></details>
|
492
|
+
"""
|
493
|
+
)
|
494
|
+
|
318
495
|
|
319
496
|
if __name__ == '__main__':
|
320
497
|
unittest.main()
|
@@ -17,10 +17,14 @@
|
|
17
17
|
# pylint: disable=g-bad-import-order
|
18
18
|
# pylint: disable=g-import-not-at-top
|
19
19
|
|
20
|
-
from langfun.core.modalities.
|
20
|
+
from langfun.core.modalities.audio import Audio
|
21
|
+
from langfun.core.modalities.mime import Mime
|
21
22
|
from langfun.core.modalities.mime import Custom
|
22
|
-
from langfun.core.modalities.
|
23
|
+
from langfun.core.modalities.ms_office import Docx
|
24
|
+
from langfun.core.modalities.ms_office import Pptx
|
25
|
+
from langfun.core.modalities.ms_office import Xlsx
|
23
26
|
from langfun.core.modalities.image import Image
|
27
|
+
from langfun.core.modalities.pdf import PDF
|
24
28
|
from langfun.core.modalities.video import Video
|
25
29
|
|
26
30
|
# pylint: enable=g-import-not-at-top
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright 2024 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
|
+
"""Audio types."""
|
15
|
+
|
16
|
+
import functools
|
17
|
+
from langfun.core.modalities import mime
|
18
|
+
|
19
|
+
|
20
|
+
class Audio(mime.Mime):
|
21
|
+
"""Audio."""
|
22
|
+
|
23
|
+
MIME_PREFIX = 'audio'
|
24
|
+
|
25
|
+
@functools.cached_property
|
26
|
+
def audio_format(self) -> str:
|
27
|
+
return self.mime_type.removeprefix(self.MIME_PREFIX + '/')
|
28
|
+
|
29
|
+
def _mime_control_for(self, uri: str) -> str:
|
30
|
+
return f'<audio controls> <source src="{uri}"> </audio>'
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright 2024 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
|
+
"""Audio tests."""
|
15
|
+
import unittest
|
16
|
+
from unittest import mock
|
17
|
+
|
18
|
+
from langfun.core.modalities import audio as audio_lib
|
19
|
+
import pyglove as pg
|
20
|
+
|
21
|
+
|
22
|
+
content_bytes = (
|
23
|
+
b'RIFF$\x00\x00\x00WAVEfmt'
|
24
|
+
b' \x10\x00\x00\x00\x01\x00\x01\x00\x00\x04\x00\x00\x00\x04\x00\x00\x01\x00\x08\x00data\x00\x00\x00\x00'
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
def mock_request(*args, **kwargs):
|
29
|
+
del args, kwargs
|
30
|
+
return pg.Dict(content=content_bytes)
|
31
|
+
|
32
|
+
|
33
|
+
class AudioTest(unittest.TestCase):
|
34
|
+
|
35
|
+
def test_audio_content(self):
|
36
|
+
audio = audio_lib.Audio.from_bytes(content_bytes)
|
37
|
+
self.assertEqual(audio.mime_type, 'audio/x-wav')
|
38
|
+
self.assertEqual(audio.audio_format, 'x-wav')
|
39
|
+
self.assertEqual(audio.to_bytes(), content_bytes)
|
40
|
+
|
41
|
+
def test_bad_audio(self):
|
42
|
+
audio = audio_lib.Audio.from_bytes(b'bad')
|
43
|
+
with self.assertRaisesRegex(ValueError, 'Expected MIME type'):
|
44
|
+
_ = audio.audio_format
|
45
|
+
|
46
|
+
|
47
|
+
class AudioFileTest(unittest.TestCase):
|
48
|
+
|
49
|
+
def test_audio_file(self):
|
50
|
+
audio = audio_lib.Audio.from_uri('http://mock/web/a.wav')
|
51
|
+
with mock.patch('requests.get') as mock_requests_get:
|
52
|
+
mock_requests_get.side_effect = mock_request
|
53
|
+
self.assertEqual(audio.audio_format, 'x-wav')
|
54
|
+
self.assertEqual(audio.mime_type, 'audio/x-wav')
|
55
|
+
self.assertEqual(
|
56
|
+
audio._raw_html(),
|
57
|
+
'<audio controls> <source src="http://mock/web/a.wav"> </audio>',
|
58
|
+
)
|
59
|
+
self.assertEqual(audio.to_bytes(), content_bytes)
|
60
|
+
|
61
|
+
|
62
|
+
if __name__ == '__main__':
|
63
|
+
unittest.main()
|
langfun/core/modalities/image.py
CHANGED
@@ -13,28 +13,47 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
"""Image modality."""
|
15
15
|
|
16
|
-
import
|
17
|
-
import
|
18
|
-
from typing import
|
16
|
+
import functools
|
17
|
+
import io
|
18
|
+
from typing import Any
|
19
|
+
|
19
20
|
from langfun.core.modalities import mime
|
20
21
|
|
22
|
+
try:
|
23
|
+
from PIL import Image as pil_image # pylint: disable=g-import-not-at-top
|
24
|
+
PILImage = pil_image.Image
|
25
|
+
pil_open = pil_image.open
|
26
|
+
except ImportError:
|
27
|
+
PILImage = Any
|
28
|
+
|
29
|
+
def pil_open(*unused_args, **unused_kwargs):
|
30
|
+
raise RuntimeError(
|
31
|
+
'Please install "langfun[mime-pil]" to enable PIL image support.'
|
32
|
+
)
|
33
|
+
|
34
|
+
|
35
|
+
class Image(mime.Mime):
|
36
|
+
"""Image."""
|
21
37
|
|
22
|
-
|
23
|
-
"""Base class for image."""
|
38
|
+
MIME_PREFIX = 'image'
|
24
39
|
|
25
|
-
@
|
40
|
+
@functools.cached_property
|
26
41
|
def image_format(self) -> str:
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
return
|
31
|
-
|
32
|
-
@
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
42
|
+
return self.mime_type.removeprefix(self.MIME_PREFIX + '/')
|
43
|
+
|
44
|
+
def _mime_control_for(self, uri: str) -> str:
|
45
|
+
return f'<img src="{uri}">'
|
46
|
+
|
47
|
+
@functools.cached_property
|
48
|
+
def size(self) -> tuple[int, int]:
|
49
|
+
img = pil_open(io.BytesIO(self.to_bytes()))
|
50
|
+
return img.size
|
51
|
+
|
52
|
+
def to_pil_image(self) -> PILImage: # pytype: disable=invalid-annotation
|
53
|
+
return pil_open(io.BytesIO(self.to_bytes()))
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def from_pil_image(cls, img: PILImage) -> 'Image': # pytype: disable=invalid-annotation
|
57
|
+
buf = io.BytesIO()
|
58
|
+
img.save(buf, format='PNG')
|
59
|
+
return cls.from_bytes(buf.getvalue())
|
@@ -12,10 +12,14 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
"""Image tests."""
|
15
|
+
import io
|
15
16
|
import unittest
|
16
17
|
from unittest import mock
|
17
18
|
|
19
|
+
import langfun.core as lf
|
18
20
|
from langfun.core.modalities import image as image_lib
|
21
|
+
from langfun.core.modalities import mime as mime_lib
|
22
|
+
import PIL.Image as pil_image
|
19
23
|
import pyglove as pg
|
20
24
|
|
21
25
|
|
@@ -36,30 +40,69 @@ def mock_request(*args, **kwargs):
|
|
36
40
|
return pg.Dict(content=image_content)
|
37
41
|
|
38
42
|
|
39
|
-
class
|
43
|
+
class ImageTest(unittest.TestCase):
|
40
44
|
|
41
|
-
def
|
45
|
+
def test_from_bytes(self):
|
42
46
|
image = image_lib.Image.from_bytes(image_content)
|
43
47
|
self.assertEqual(image.image_format, 'png')
|
44
|
-
self.assertIn('data:image/png;base64,', image.
|
48
|
+
self.assertIn('data:image/png;base64,', image._raw_html())
|
45
49
|
self.assertEqual(image.to_bytes(), image_content)
|
50
|
+
with self.assertRaisesRegex(
|
51
|
+
lf.ModalityError, '.* cannot be converted to text'
|
52
|
+
):
|
53
|
+
image.to_text()
|
46
54
|
|
47
|
-
def
|
55
|
+
def test_from_bytes_invalid(self):
|
48
56
|
image = image_lib.Image.from_bytes(b'bad')
|
49
|
-
with self.assertRaisesRegex(ValueError, '
|
57
|
+
with self.assertRaisesRegex(ValueError, 'Expected MIME type'):
|
50
58
|
_ = image.image_format
|
51
59
|
|
60
|
+
def test_from_bytes_base_cls(self):
|
61
|
+
self.assertIsInstance(
|
62
|
+
mime_lib.Mime.from_bytes(image_content), image_lib.Image
|
63
|
+
)
|
52
64
|
|
53
|
-
|
54
|
-
|
55
|
-
def test_image_file(self):
|
65
|
+
def test_from_uri(self):
|
56
66
|
image = image_lib.Image.from_uri('http://mock/web/a.png')
|
57
67
|
with mock.patch('requests.get') as mock_requests_get:
|
58
68
|
mock_requests_get.side_effect = mock_request
|
59
69
|
self.assertEqual(image.image_format, 'png')
|
60
|
-
self.assertEqual(
|
70
|
+
self.assertEqual(
|
71
|
+
image._raw_html(),
|
72
|
+
'<img src="http://mock/web/a.png">'
|
73
|
+
)
|
74
|
+
self.assertEqual(image.to_bytes(), image_content)
|
75
|
+
|
76
|
+
def test_from_uri_base_cls(self):
|
77
|
+
with mock.patch('requests.get') as mock_requests_get:
|
78
|
+
mock_requests_get.side_effect = mock_request
|
79
|
+
image = mime_lib.Mime.from_uri('http://mock/web/a.png')
|
80
|
+
self.assertIsInstance(image, image_lib.Image)
|
81
|
+
self.assertEqual(image.image_format, 'png')
|
82
|
+
self.assertEqual(
|
83
|
+
image._raw_html(),
|
84
|
+
'<img src="http://mock/web/a.png">'
|
85
|
+
)
|
61
86
|
self.assertEqual(image.to_bytes(), image_content)
|
62
87
|
|
88
|
+
def test_image_size(self):
|
89
|
+
image = image_lib.Image.from_uri('http://mock/web/a.png')
|
90
|
+
with mock.patch('requests.get') as mock_requests_get:
|
91
|
+
mock_requests_get.side_effect = mock_request
|
92
|
+
self.assertEqual(image.size, (24, 24))
|
93
|
+
|
94
|
+
def test_to_pil_image(self):
|
95
|
+
image = image_lib.Image.from_uri('http://mock/web/a.png')
|
96
|
+
with mock.patch('requests.get') as mock_requests_get:
|
97
|
+
mock_requests_get.side_effect = mock_request
|
98
|
+
self.assertIsInstance(image.to_pil_image(), pil_image.Image)
|
99
|
+
|
100
|
+
def test_from_pil_image(self):
|
101
|
+
image = pil_image.open(io.BytesIO(image_content))
|
102
|
+
self.assertIsInstance(
|
103
|
+
image_lib.Image.from_pil_image(image), image_lib.Image
|
104
|
+
)
|
105
|
+
|
63
106
|
|
64
107
|
if __name__ == '__main__':
|
65
108
|
unittest.main()
|