Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions tensorboard/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ py_library(
],
)

py_test(
name = "lib_test",
size = "small",
srcs = ["lib_test.py"],
srcs_version = "PY2AND3",
visibility = ["//tensorboard:internal"],
deps = [
":lib",
"@org_pythonhosted_six",
],
)

py_library(
name = "manager",
srcs = ["manager.py"],
Expand Down
55 changes: 49 additions & 6 deletions tensorboard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,62 @@
from tensorboard import lazy


# Please be careful when changing the structure of this file.
#
# The lazy imports in this file must use `importlib.import_module`, not
# `import tensorboard.foo` or `from tensorboard import foo`, or it will
# be impossible to reload the TensorBoard module without breaking these
# top-level public APIs. This has to do with the gory details of
# Python's module system. Take `tensorboard.notebook` as an example:
#
# - When the `tensorboard` module (that's us!) is initialized, its
# `notebook` attribute is initialized to a new LazyModule. The
# actual `tensorboard.notebook` submodule is not loaded.
#
# - When the `tensorboard.notebook` submodule is first loaded, Python
# _reassigns_ the `notebook` attribute on the `tensorboard` module
# object to point to the underlying `tensorboard.notebook` module
# object, rather than its former LazyModule value. This occurs
# whether the module is loaded via the lazy module or directly as an
# import:
#
# - import tensorboard; tensorboard.notebook.start(...) # one way
# - from tensorboard import notebook # other way; same effect
#
# - When the `tensorboard` module is reloaded, its `notebook`
# attribute is once again bound to a (new) LazyModule, while the
# `tensorboard.notebook` module object is unaffected and still
# exists in `sys.modules`. But then...
#
# - When the new LazyModule is forced, it must resolve to the existing
# `tensorboard.notebook` module object rather than itself (which
# just creates a stack overflow). If the LazyModule load function
# uses `import tensorboard.notebook; return tensorboard.notebook`,
# then the first statement will do _nothing_ because the
# `tensorboard.notebook` module is already loaded, and the second
# statement will return the LazyModule itself. The same goes for the
# `from tensorboard import notebook` form. We need to ensure that
# the submodule is loaded and then pull the actual module object out
# of `sys.modules`... which is exactly what `importlib` handles for
# us.
#
# See <https://github.com/tensorflow/tensorboard/issues/1989> for
# additional discussion.


@lazy.lazy_load('tensorboard.notebook')
def notebook():
import tensorboard.notebook as module # pylint: disable=g-import-not-at-top
return module
import importlib # pylint: disable=g-import-not-at-top
return importlib.import_module('tensorboard.notebook')


@lazy.lazy_load('tensorboard.program')
def program():
import tensorboard.program as module # pylint: disable=g-import-not-at-top
return module
import importlib # pylint: disable=g-import-not-at-top
return importlib.import_module('tensorboard.program')


@lazy.lazy_load('tensorboard.summary')
def summary():
import tensorboard.summary as module # pylint: disable=g-import-not-at-top
return module
import importlib # pylint: disable=g-import-not-at-top
return importlib.import_module('tensorboard.summary')
43 changes: 43 additions & 0 deletions tensorboard/lib_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from six import moves
import sys
import unittest


class ReloadTensorBoardTest(unittest.TestCase):

def test_functional_after_reload(self):
self.assertNotIn("tensorboard", sys.modules)
import tensorboard as tensorboard # it makes the Google sync happy
submodules = ["notebook", "program", "summary"]
dirs_before = {
module_name: dir(getattr(tensorboard, module_name))
for module_name in submodules
}
tensorboard = moves.reload_module(tensorboard)
dirs_after = {
module_name: dir(getattr(tensorboard, module_name))
for module_name in submodules
}
self.assertEqual(dirs_before, dirs_after)


if __name__ == '__main__':
unittest.main()