pyglove 0.5.0.dev202508250811__py3-none-any.whl → 0.5.0.dev202511300809__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.
- pyglove/core/__init__.py +8 -1
- pyglove/core/geno/base.py +7 -3
- pyglove/core/io/file_system.py +295 -2
- pyglove/core/io/file_system_test.py +291 -0
- pyglove/core/logging.py +45 -1
- pyglove/core/logging_test.py +12 -21
- pyglove/core/monitoring.py +657 -0
- pyglove/core/monitoring_test.py +289 -0
- pyglove/core/symbolic/__init__.py +7 -0
- pyglove/core/symbolic/base.py +89 -35
- pyglove/core/symbolic/base_test.py +3 -3
- pyglove/core/symbolic/dict.py +31 -12
- pyglove/core/symbolic/dict_test.py +49 -0
- pyglove/core/symbolic/list.py +17 -3
- pyglove/core/symbolic/list_test.py +24 -2
- pyglove/core/symbolic/object.py +3 -1
- pyglove/core/symbolic/object_test.py +13 -10
- pyglove/core/symbolic/ref.py +19 -7
- pyglove/core/symbolic/ref_test.py +94 -7
- pyglove/core/symbolic/unknown_symbols.py +147 -0
- pyglove/core/symbolic/unknown_symbols_test.py +100 -0
- pyglove/core/typing/annotation_conversion.py +8 -1
- pyglove/core/typing/annotation_conversion_test.py +14 -19
- pyglove/core/typing/class_schema.py +24 -1
- pyglove/core/typing/json_schema.py +221 -8
- pyglove/core/typing/json_schema_test.py +508 -12
- pyglove/core/typing/type_conversion.py +17 -3
- pyglove/core/typing/type_conversion_test.py +7 -2
- pyglove/core/typing/value_specs.py +5 -1
- pyglove/core/typing/value_specs_test.py +5 -0
- pyglove/core/utils/__init__.py +2 -0
- pyglove/core/utils/contextual.py +9 -4
- pyglove/core/utils/contextual_test.py +10 -0
- pyglove/core/utils/error_utils.py +59 -25
- pyglove/core/utils/json_conversion.py +360 -63
- pyglove/core/utils/json_conversion_test.py +146 -13
- pyglove/core/views/html/controls/tab.py +33 -0
- pyglove/core/views/html/controls/tab_test.py +37 -0
- pyglove/ext/evolution/base_test.py +1 -1
- {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/METADATA +8 -1
- {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/RECORD +44 -40
- {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/top_level.txt +0 -0
|
@@ -16,6 +16,8 @@ import os
|
|
|
16
16
|
import pathlib
|
|
17
17
|
import tempfile
|
|
18
18
|
import unittest
|
|
19
|
+
from unittest import mock
|
|
20
|
+
import fsspec
|
|
19
21
|
from pyglove.core.io import file_system
|
|
20
22
|
|
|
21
23
|
|
|
@@ -82,6 +84,75 @@ class StdFileSystemTest(unittest.TestCase):
|
|
|
82
84
|
fs.rmdirs(os.path.join(dir_a, 'b/c'))
|
|
83
85
|
self.assertEqual(sorted(fs.listdir(dir_a)), ['file1']) # pylint: disable=g-generic-assert
|
|
84
86
|
|
|
87
|
+
def test_rename(self):
|
|
88
|
+
tmp_dir = tempfile.mkdtemp()
|
|
89
|
+
fs = file_system.StdFileSystem()
|
|
90
|
+
|
|
91
|
+
_ = fs.mkdirs(os.path.join(tmp_dir, 'a/b'))
|
|
92
|
+
file_foo = os.path.join(tmp_dir, 'a/foo.txt')
|
|
93
|
+
file_bar = os.path.join(tmp_dir, 'a/bar.txt')
|
|
94
|
+
|
|
95
|
+
with fs.open(file_foo, 'w') as f:
|
|
96
|
+
f.write('foo')
|
|
97
|
+
with fs.open(file_bar, 'w') as f:
|
|
98
|
+
f.write('bar')
|
|
99
|
+
|
|
100
|
+
# Rename file to a new name.
|
|
101
|
+
file_foo_new = os.path.join(tmp_dir, 'a/foo-new.txt')
|
|
102
|
+
fs.rename(file_foo, file_foo_new)
|
|
103
|
+
self.assertFalse(fs.exists(file_foo))
|
|
104
|
+
self.assertTrue(fs.exists(file_foo_new))
|
|
105
|
+
|
|
106
|
+
# Rename file to an existing file name.
|
|
107
|
+
fs.rename(file_foo_new, file_bar)
|
|
108
|
+
self.assertFalse(fs.exists(file_foo_new))
|
|
109
|
+
with fs.open(file_bar, 'r') as f:
|
|
110
|
+
self.assertEqual(f.read(), 'foo')
|
|
111
|
+
|
|
112
|
+
# Rename directory to a new name.
|
|
113
|
+
dir_b = os.path.join(tmp_dir, 'a/b')
|
|
114
|
+
dir_c = os.path.join(tmp_dir, 'a/c')
|
|
115
|
+
fs.rename(dir_b, dir_c)
|
|
116
|
+
self.assertFalse(fs.exists(dir_b))
|
|
117
|
+
self.assertTrue(fs.exists(dir_c))
|
|
118
|
+
self.assertTrue(fs.isdir(dir_c))
|
|
119
|
+
|
|
120
|
+
# Rename directory to an existing empty directory.
|
|
121
|
+
dir_d = os.path.join(tmp_dir, 'a/d')
|
|
122
|
+
fs.mkdirs(dir_d)
|
|
123
|
+
fs.rename(dir_c, dir_d)
|
|
124
|
+
self.assertFalse(fs.exists(dir_c))
|
|
125
|
+
self.assertTrue(fs.exists(dir_d))
|
|
126
|
+
|
|
127
|
+
# Rename directory to a non-empty directory.
|
|
128
|
+
dir_x = os.path.join(tmp_dir, 'x')
|
|
129
|
+
dir_a = os.path.join(tmp_dir, 'a')
|
|
130
|
+
fs.mkdirs(os.path.join(dir_x, 'y'))
|
|
131
|
+
with self.assertRaises(OSError):
|
|
132
|
+
fs.rename(dir_a, dir_x)
|
|
133
|
+
self.assertTrue(fs.exists(dir_a))
|
|
134
|
+
self.assertTrue(fs.exists(os.path.join(dir_x, 'y')))
|
|
135
|
+
|
|
136
|
+
# Errors
|
|
137
|
+
dir_u = os.path.join(tmp_dir, 'u')
|
|
138
|
+
dir_u_v = os.path.join(dir_u, 'v')
|
|
139
|
+
file_u_a = os.path.join(dir_u, 'a.txt')
|
|
140
|
+
fs.mkdirs(dir_u_v)
|
|
141
|
+
with fs.open(file_u_a, 'w') as f:
|
|
142
|
+
f.write('a')
|
|
143
|
+
|
|
144
|
+
with self.assertRaises((OSError, NotADirectoryError)):
|
|
145
|
+
fs.rename(dir_u, file_u_a)
|
|
146
|
+
|
|
147
|
+
with self.assertRaises(IsADirectoryError):
|
|
148
|
+
fs.rename(file_u_a, dir_u_v)
|
|
149
|
+
|
|
150
|
+
with self.assertRaises(FileNotFoundError):
|
|
151
|
+
fs.rename(
|
|
152
|
+
os.path.join(tmp_dir, 'non-existent'),
|
|
153
|
+
os.path.join(tmp_dir, 'y')
|
|
154
|
+
)
|
|
155
|
+
|
|
85
156
|
|
|
86
157
|
class MemoryFileSystemTest(unittest.TestCase):
|
|
87
158
|
|
|
@@ -180,6 +251,94 @@ class MemoryFileSystemTest(unittest.TestCase):
|
|
|
180
251
|
fs.rmdirs(os.path.join(dir_a, 'b/c'))
|
|
181
252
|
self.assertEqual(fs.listdir(dir_a), ['file1']) # pylint: disable=g-generic-assert
|
|
182
253
|
|
|
254
|
+
def test_glob(self):
|
|
255
|
+
fs = file_system.MemoryFileSystem()
|
|
256
|
+
fs.mkdirs('/mem/a/b/c')
|
|
257
|
+
with fs.open('/mem/a/foo.txt', 'w') as f:
|
|
258
|
+
f.write('foo')
|
|
259
|
+
with fs.open('/mem/a/bar.json', 'w') as f:
|
|
260
|
+
f.write('bar')
|
|
261
|
+
with fs.open('/mem/a/b/baz.txt', 'w') as f:
|
|
262
|
+
f.write('baz')
|
|
263
|
+
|
|
264
|
+
self.assertEqual(
|
|
265
|
+
sorted(fs.glob('/mem/a/*')),
|
|
266
|
+
['/mem/a/b', '/mem/a/b/baz.txt', '/mem/a/b/c',
|
|
267
|
+
'/mem/a/bar.json', '/mem/a/foo.txt'])
|
|
268
|
+
self.assertEqual(
|
|
269
|
+
sorted(fs.glob('/mem/a/*.txt')),
|
|
270
|
+
['/mem/a/b/baz.txt', '/mem/a/foo.txt'])
|
|
271
|
+
self.assertEqual(
|
|
272
|
+
sorted(fs.glob('/mem/a/b/*')),
|
|
273
|
+
['/mem/a/b/baz.txt', '/mem/a/b/c'])
|
|
274
|
+
self.assertEqual(fs.glob('/mem/a/b/*.txt'), ['/mem/a/b/baz.txt'])
|
|
275
|
+
self.assertEqual(fs.glob('/mem/a/b/c/*'), [])
|
|
276
|
+
self.assertEqual(fs.glob('/mem/a/???.txt'), ['/mem/a/foo.txt'])
|
|
277
|
+
self.assertEqual(fs.glob('/mem/a/bar.*'), ['/mem/a/bar.json'])
|
|
278
|
+
self.assertEqual(
|
|
279
|
+
sorted(fs.glob('/mem/a/*.*')),
|
|
280
|
+
['/mem/a/b/baz.txt', '/mem/a/bar.json', '/mem/a/foo.txt'])
|
|
281
|
+
|
|
282
|
+
def test_rename(self):
|
|
283
|
+
fs = file_system.MemoryFileSystem()
|
|
284
|
+
fs.mkdirs('/mem/a/b')
|
|
285
|
+
with fs.open('/mem/a/foo.txt', 'w') as f:
|
|
286
|
+
f.write('foo')
|
|
287
|
+
with fs.open('/mem/a/bar.txt', 'w') as f:
|
|
288
|
+
f.write('bar')
|
|
289
|
+
|
|
290
|
+
# Rename file to a new name.
|
|
291
|
+
fs.rename('/mem/a/foo.txt', '/mem/a/foo-new.txt')
|
|
292
|
+
self.assertFalse(fs.exists('/mem/a/foo.txt'))
|
|
293
|
+
self.assertTrue(fs.exists('/mem/a/foo-new.txt'))
|
|
294
|
+
|
|
295
|
+
# Rename file to an existing file name.
|
|
296
|
+
fs.rename('/mem/a/foo-new.txt', '/mem/a/bar.txt')
|
|
297
|
+
self.assertFalse(fs.exists('/mem/a/foo-new.txt'))
|
|
298
|
+
with fs.open('/mem/a/bar.txt', 'r') as f:
|
|
299
|
+
self.assertEqual(f.read(), 'foo')
|
|
300
|
+
|
|
301
|
+
# Rename directory to a new name.
|
|
302
|
+
fs.rename('/mem/a/b', '/mem/a/c')
|
|
303
|
+
self.assertFalse(fs.exists('/mem/a/b'))
|
|
304
|
+
self.assertTrue(fs.exists('/mem/a/c'))
|
|
305
|
+
self.assertTrue(fs.isdir('/mem/a/c'))
|
|
306
|
+
|
|
307
|
+
# Rename directory to an existing empty directory.
|
|
308
|
+
fs.mkdirs('/mem/a/d')
|
|
309
|
+
fs.rename('/mem/a/c', '/mem/a/d')
|
|
310
|
+
self.assertFalse(fs.exists('/mem/a/c'))
|
|
311
|
+
self.assertTrue(fs.exists('/mem/a/d'))
|
|
312
|
+
|
|
313
|
+
# Rename directory to a non-empty directory.
|
|
314
|
+
fs.mkdirs('/mem/x/y')
|
|
315
|
+
with self.assertRaisesRegex(OSError, "Directory not empty: '/mem/x'"):
|
|
316
|
+
fs.rename('/mem/a', '/mem/x')
|
|
317
|
+
self.assertTrue(fs.exists('/mem/a'))
|
|
318
|
+
self.assertTrue(fs.exists('/mem/x/y'))
|
|
319
|
+
|
|
320
|
+
# Errors
|
|
321
|
+
fs.mkdirs('/mem/u/v')
|
|
322
|
+
with fs.open('/mem/u/a.txt', 'w') as f:
|
|
323
|
+
f.write('a')
|
|
324
|
+
|
|
325
|
+
with self.assertRaisesRegex(
|
|
326
|
+
OSError, "Cannot move directory '/mem/u' to a subdirectory of itself"):
|
|
327
|
+
fs.rename('/mem/u', '/mem/u/v/w')
|
|
328
|
+
|
|
329
|
+
with self.assertRaisesRegex(
|
|
330
|
+
NotADirectoryError,
|
|
331
|
+
"Cannot rename directory '/mem/u' to non-directory '/mem/u/a.txt'"):
|
|
332
|
+
fs.rename('/mem/u', '/mem/u/a.txt')
|
|
333
|
+
|
|
334
|
+
with self.assertRaisesRegex(
|
|
335
|
+
IsADirectoryError,
|
|
336
|
+
"Cannot rename non-directory '/mem/u/a.txt' to directory '/mem/u/v'"):
|
|
337
|
+
fs.rename('/mem/u/a.txt', '/mem/u/v')
|
|
338
|
+
|
|
339
|
+
with self.assertRaises(FileNotFoundError):
|
|
340
|
+
fs.rename('/mem/non-existent', '/mem/y')
|
|
341
|
+
|
|
183
342
|
|
|
184
343
|
class FileIoApiTest(unittest.TestCase):
|
|
185
344
|
|
|
@@ -217,6 +376,14 @@ class FileIoApiTest(unittest.TestCase):
|
|
|
217
376
|
file_system.rm(file2)
|
|
218
377
|
self.assertFalse(file_system.path_exists(file2))
|
|
219
378
|
|
|
379
|
+
# Test glob with standard file system.
|
|
380
|
+
glob_dir = os.path.join(tempfile.mkdtemp(), 'glob')
|
|
381
|
+
file_system.mkdirs(os.path.join(glob_dir, 'a/b'))
|
|
382
|
+
file_system.writefile(os.path.join(glob_dir, 'a/foo.txt'), 'foo')
|
|
383
|
+
self.assertEqual(
|
|
384
|
+
sorted(file_system.glob(os.path.join(glob_dir, 'a/*'))),
|
|
385
|
+
[os.path.join(glob_dir, 'a/b'), os.path.join(glob_dir, 'a/foo.txt')])
|
|
386
|
+
|
|
220
387
|
def test_memory_filesystem(self):
|
|
221
388
|
file1 = pathlib.Path('/mem/file1')
|
|
222
389
|
with self.assertRaises(FileNotFoundError):
|
|
@@ -248,6 +415,130 @@ class FileIoApiTest(unittest.TestCase):
|
|
|
248
415
|
file_system.rm(file2)
|
|
249
416
|
self.assertFalse(file_system.path_exists(file2))
|
|
250
417
|
|
|
418
|
+
# Test glob with memory file system.
|
|
419
|
+
file_system.mkdirs('/mem/g/a/b')
|
|
420
|
+
file_system.writefile('/mem/g/a/foo.txt', 'foo')
|
|
421
|
+
file_system.rename('/mem/g/a/foo.txt', '/mem/g/a/foo2.txt')
|
|
422
|
+
file_system.writefile('/mem/g/a/b/bar.txt', 'bar')
|
|
423
|
+
self.assertEqual(
|
|
424
|
+
sorted(file_system.glob('/mem/g/a/*')),
|
|
425
|
+
['/mem/g/a/b', '/mem/g/a/b/bar.txt', '/mem/g/a/foo2.txt'])
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class FsspecFileSystemTest(unittest.TestCase):
|
|
429
|
+
|
|
430
|
+
def setUp(self):
|
|
431
|
+
super().setUp()
|
|
432
|
+
self.fs = fsspec.filesystem('memory')
|
|
433
|
+
self.fs.pipe('memory:///a/b/c', b'abc')
|
|
434
|
+
self.fs.pipe('memory:///a/b/d', b'abd')
|
|
435
|
+
self.fs.mkdir('memory:///a/e')
|
|
436
|
+
|
|
437
|
+
def tearDown(self):
|
|
438
|
+
super().tearDown()
|
|
439
|
+
fsspec.filesystem('memory').rm('/', recursive=True)
|
|
440
|
+
|
|
441
|
+
def test_read_file(self):
|
|
442
|
+
self.assertEqual(file_system.readfile('memory:///a/b/c', mode='rb'), b'abc')
|
|
443
|
+
with file_system.open('memory:///a/b/d', 'rb') as f:
|
|
444
|
+
self.assertEqual(f.read(), b'abd')
|
|
445
|
+
|
|
446
|
+
def test_fsspec_file_ops(self):
|
|
447
|
+
file_system.writefile('memory:///f', b'hello\nworld\n', mode='wb')
|
|
448
|
+
with file_system.open('memory:///f', 'rb') as f:
|
|
449
|
+
self.assertIsInstance(f, file_system.FsspecFile)
|
|
450
|
+
self.assertEqual(f.readline(), b'hello\n')
|
|
451
|
+
self.assertEqual(f.tell(), 6)
|
|
452
|
+
self.assertEqual(f.seek(8), 8)
|
|
453
|
+
self.assertEqual(f.read(), b'rld\n')
|
|
454
|
+
f.flush()
|
|
455
|
+
|
|
456
|
+
def test_write_file(self):
|
|
457
|
+
file_system.writefile('memory:///a/b/e', b'abe', mode='wb')
|
|
458
|
+
self.assertTrue(self.fs.exists('memory:///a/b/e'))
|
|
459
|
+
self.assertEqual(self.fs.cat('memory:///a/b/e'), b'abe')
|
|
460
|
+
|
|
461
|
+
def test_exists(self):
|
|
462
|
+
self.assertTrue(file_system.path_exists('memory:///a/b/c'))
|
|
463
|
+
self.assertFalse(file_system.path_exists('memory:///a/b/nonexist'))
|
|
464
|
+
|
|
465
|
+
def test_isdir(self):
|
|
466
|
+
self.assertTrue(file_system.isdir('memory:///a/b'))
|
|
467
|
+
self.assertTrue(file_system.isdir('memory:///a/e'))
|
|
468
|
+
self.assertFalse(file_system.isdir('memory:///a/b/c'))
|
|
469
|
+
|
|
470
|
+
def test_listdir(self):
|
|
471
|
+
self.assertCountEqual(file_system.listdir('memory:///a'), ['b', 'e'])
|
|
472
|
+
self.assertCountEqual(file_system.listdir('memory:///a/b'), ['c', 'd'])
|
|
473
|
+
|
|
474
|
+
def test_glob(self):
|
|
475
|
+
self.assertCountEqual(
|
|
476
|
+
file_system.glob('memory:///a/b/*'),
|
|
477
|
+
['memory:///a/b/c', 'memory:///a/b/d']
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
def test_mkdir(self):
|
|
481
|
+
file_system.mkdir('memory:///a/f')
|
|
482
|
+
self.assertTrue(self.fs.isdir('memory:///a/f'))
|
|
483
|
+
|
|
484
|
+
def test_mkdirs(self):
|
|
485
|
+
file_system.mkdirs('memory:///g/h/i')
|
|
486
|
+
self.assertTrue(self.fs.isdir('memory:///g/h/i'))
|
|
487
|
+
|
|
488
|
+
def test_rm(self):
|
|
489
|
+
file_system.rm('memory:///a/b/c')
|
|
490
|
+
self.assertFalse(self.fs.exists('memory:///a/b/c'))
|
|
491
|
+
|
|
492
|
+
def test_rename(self):
|
|
493
|
+
file_system.rename('memory:///a/b/c', 'memory:///a/b/c_new')
|
|
494
|
+
self.assertFalse(self.fs.exists('memory:///a/b/c'))
|
|
495
|
+
self.assertTrue(self.fs.exists('memory:///a/b/c_new'))
|
|
496
|
+
with self.assertRaisesRegex(ValueError, 'Rename across different'):
|
|
497
|
+
file_system.rename('memory:///a/b/c_new', 'file:///a/b/c_d')
|
|
498
|
+
|
|
499
|
+
def test_chmod(self):
|
|
500
|
+
mock_fs = mock.Mock()
|
|
501
|
+
mock_fs.chmod = mock.Mock()
|
|
502
|
+
with mock.patch('fsspec.core.url_to_fs', return_value=(mock_fs, 'path')):
|
|
503
|
+
file_system.chmod('protocol:///path', 0o777)
|
|
504
|
+
mock_fs.chmod.assert_called_once_with('path', 0o777)
|
|
505
|
+
|
|
506
|
+
def test_rmdir(self):
|
|
507
|
+
file_system.rmdir('memory:///a/e')
|
|
508
|
+
self.assertFalse(self.fs.exists('memory:///a/e'))
|
|
509
|
+
|
|
510
|
+
def test_rmdirs(self):
|
|
511
|
+
file_system.mkdirs('memory:///x/y/z')
|
|
512
|
+
self.assertTrue(file_system.isdir('memory:///x/y/z'))
|
|
513
|
+
file_system.rmdirs('memory:///x')
|
|
514
|
+
self.assertFalse(file_system.path_exists('memory:///x'))
|
|
515
|
+
|
|
516
|
+
def test_fsspec_uri_catcher(self):
|
|
517
|
+
with mock.patch.object(
|
|
518
|
+
file_system.FsspecFileSystem, 'exists', return_value=True
|
|
519
|
+
) as mock_fsspec_exists:
|
|
520
|
+
# We use a protocol that is not registered in
|
|
521
|
+
# fsspec.available_protocols() to make sure _FsspecUriCatcher is used.
|
|
522
|
+
self.assertTrue(file_system.path_exists('some-proto://foo'))
|
|
523
|
+
mock_fsspec_exists.assert_called_once_with('some-proto://foo')
|
|
524
|
+
|
|
525
|
+
# For full coverage of _FsspecUriCatcher.get_fs returning StdFileSystem,
|
|
526
|
+
# we need to test a non-URI path that doesn't match other prefixes.
|
|
527
|
+
# We mock StdFileSystem.exists to check if it's called.
|
|
528
|
+
with mock.patch.object(
|
|
529
|
+
file_system.StdFileSystem, 'exists', return_value=True
|
|
530
|
+
) as mock_std_exists:
|
|
531
|
+
self.assertTrue(file_system.path_exists('/foo/bar/baz'))
|
|
532
|
+
mock_std_exists.assert_called_once_with('/foo/bar/baz')
|
|
533
|
+
|
|
534
|
+
with mock.patch.object(
|
|
535
|
+
file_system.FsspecFileSystem, 'rename', return_value=None
|
|
536
|
+
) as mock_fsspec_rename:
|
|
537
|
+
file_system.rename('some-proto://foo', 'some-proto://bar')
|
|
538
|
+
mock_fsspec_rename.assert_called_once_with(
|
|
539
|
+
'some-proto://foo', 'some-proto://bar'
|
|
540
|
+
)
|
|
541
|
+
|
|
251
542
|
|
|
252
543
|
if __name__ == '__main__':
|
|
253
544
|
unittest.main()
|
pyglove/core/logging.py
CHANGED
|
@@ -17,9 +17,11 @@ This module allows PyGlove to use external created logger for logging PyGlove
|
|
|
17
17
|
events without introducing library dependencies in PyGlove.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
+
import contextlib
|
|
20
21
|
import inspect
|
|
21
22
|
import logging
|
|
22
|
-
|
|
23
|
+
import sys
|
|
24
|
+
from typing import Any, Callable, Iterator, List, Union
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
_DEFAULT_LOGGER = logging.getLogger()
|
|
@@ -117,3 +119,45 @@ def critical(msg: str, *args, **kwargs) -> None:
|
|
|
117
119
|
**kwargs: Keyword arguments for the logger.
|
|
118
120
|
"""
|
|
119
121
|
_DEFAULT_LOGGER.critical(msg, *args, **kwargs)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def use_stream(
|
|
125
|
+
stream: Any,
|
|
126
|
+
level: int = logging.INFO,
|
|
127
|
+
name: str = 'custom',
|
|
128
|
+
fmt: str = '{levelname:8} | {asctime} | {message}',
|
|
129
|
+
datefmt: str = '%Y-%m-%d %H:%M:%S') -> logging.Logger:
|
|
130
|
+
"""Use stdout for logging."""
|
|
131
|
+
logger = logging.getLogger(name)
|
|
132
|
+
logger.setLevel(level)
|
|
133
|
+
stdout_handler = logging.StreamHandler(stream=stream)
|
|
134
|
+
stdout_handler.setLevel(level)
|
|
135
|
+
stdout_handler.setFormatter(
|
|
136
|
+
logging.Formatter(fmt=fmt, datefmt=datefmt, style='{'))
|
|
137
|
+
logger.addHandler(stdout_handler)
|
|
138
|
+
set_logger(logger)
|
|
139
|
+
return logger
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def use_stdout(
|
|
143
|
+
level: int = logging.INFO,
|
|
144
|
+
fmt: str = '{levelname:8} | {asctime} | {message}',
|
|
145
|
+
datefmt: str = '%Y-%m-%d %H:%M:%S') -> logging.Logger:
|
|
146
|
+
"""Use stdout for logging."""
|
|
147
|
+
return use_stream(sys.stdout, level, 'stdout', fmt, datefmt)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@contextlib.contextmanager
|
|
151
|
+
def redirect_stream(
|
|
152
|
+
stream: Any,
|
|
153
|
+
level: int = logging.INFO,
|
|
154
|
+
name: str = 'custom',
|
|
155
|
+
fmt: str = '{levelname:8} | {asctime} | {message}',
|
|
156
|
+
datefmt: str = '%Y-%m-%d %H:%M:%S') -> Iterator[logging.Logger]:
|
|
157
|
+
"""Redirect the stream to the given logger."""
|
|
158
|
+
previous_logger = get_logger()
|
|
159
|
+
try:
|
|
160
|
+
logger = use_stream(stream, level, name, fmt, datefmt)
|
|
161
|
+
yield logger
|
|
162
|
+
finally:
|
|
163
|
+
set_logger(previous_logger)
|
pyglove/core/logging_test.py
CHANGED
|
@@ -21,15 +21,9 @@ class LoggingTest(unittest.TestCase):
|
|
|
21
21
|
"""Tests for pg.logging."""
|
|
22
22
|
|
|
23
23
|
def testLogging(self):
|
|
24
|
-
string_io = io.StringIO()
|
|
25
|
-
logger = logging.getLogger('logger1')
|
|
26
|
-
logger.setLevel(logging.INFO)
|
|
27
|
-
console_handler = logging.StreamHandler(stream=string_io)
|
|
28
|
-
console_handler.setLevel(logging.INFO)
|
|
29
|
-
logger.addHandler(console_handler)
|
|
30
|
-
|
|
31
24
|
self.assertIs(pg_logging.get_logger(), logging.getLogger())
|
|
32
|
-
|
|
25
|
+
string_io = io.StringIO()
|
|
26
|
+
logger = pg_logging.use_stream(string_io, name='logger1', fmt='')
|
|
33
27
|
self.assertIs(pg_logging.get_logger(), logger)
|
|
34
28
|
|
|
35
29
|
pg_logging.debug('x=%s', 1)
|
|
@@ -46,19 +40,16 @@ class LoggingTest(unittest.TestCase):
|
|
|
46
40
|
]) + '\n')
|
|
47
41
|
|
|
48
42
|
string_io = io.StringIO()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
pg_logging.
|
|
58
|
-
|
|
59
|
-
'x=6',
|
|
60
|
-
]) + '\n')
|
|
61
|
-
|
|
43
|
+
with pg_logging.redirect_stream(
|
|
44
|
+
string_io, level=logging.DEBUG, name='logger2', fmt=''
|
|
45
|
+
):
|
|
46
|
+
pg_logging.debug('x=%s', 6)
|
|
47
|
+
self.assertEqual(string_io.getvalue(), '\n'.join([
|
|
48
|
+
'x=6',
|
|
49
|
+
]) + '\n')
|
|
50
|
+
|
|
51
|
+
pg_logging.use_stdout()
|
|
52
|
+
pg_logging.info('y=%s', 7)
|
|
62
53
|
|
|
63
54
|
if __name__ == '__main__':
|
|
64
55
|
unittest.main()
|