tensorbored 2.21.0rc1769983804__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 (271) hide show
  1. tensorbored/__init__.py +112 -0
  2. tensorbored/_vendor/__init__.py +0 -0
  3. tensorbored/_vendor/bleach/__init__.py +125 -0
  4. tensorbored/_vendor/bleach/_vendor/__init__.py +0 -0
  5. tensorbored/_vendor/bleach/_vendor/html5lib/__init__.py +35 -0
  6. tensorbored/_vendor/bleach/_vendor/html5lib/_ihatexml.py +289 -0
  7. tensorbored/_vendor/bleach/_vendor/html5lib/_inputstream.py +918 -0
  8. tensorbored/_vendor/bleach/_vendor/html5lib/_tokenizer.py +1735 -0
  9. tensorbored/_vendor/bleach/_vendor/html5lib/_trie/__init__.py +5 -0
  10. tensorbored/_vendor/bleach/_vendor/html5lib/_trie/_base.py +40 -0
  11. tensorbored/_vendor/bleach/_vendor/html5lib/_trie/py.py +67 -0
  12. tensorbored/_vendor/bleach/_vendor/html5lib/_utils.py +159 -0
  13. tensorbored/_vendor/bleach/_vendor/html5lib/constants.py +2946 -0
  14. tensorbored/_vendor/bleach/_vendor/html5lib/filters/__init__.py +0 -0
  15. tensorbored/_vendor/bleach/_vendor/html5lib/filters/alphabeticalattributes.py +29 -0
  16. tensorbored/_vendor/bleach/_vendor/html5lib/filters/base.py +12 -0
  17. tensorbored/_vendor/bleach/_vendor/html5lib/filters/inject_meta_charset.py +73 -0
  18. tensorbored/_vendor/bleach/_vendor/html5lib/filters/lint.py +93 -0
  19. tensorbored/_vendor/bleach/_vendor/html5lib/filters/optionaltags.py +207 -0
  20. tensorbored/_vendor/bleach/_vendor/html5lib/filters/sanitizer.py +916 -0
  21. tensorbored/_vendor/bleach/_vendor/html5lib/filters/whitespace.py +38 -0
  22. tensorbored/_vendor/bleach/_vendor/html5lib/html5parser.py +2795 -0
  23. tensorbored/_vendor/bleach/_vendor/html5lib/serializer.py +409 -0
  24. tensorbored/_vendor/bleach/_vendor/html5lib/treeadapters/__init__.py +30 -0
  25. tensorbored/_vendor/bleach/_vendor/html5lib/treeadapters/genshi.py +54 -0
  26. tensorbored/_vendor/bleach/_vendor/html5lib/treeadapters/sax.py +50 -0
  27. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/__init__.py +88 -0
  28. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/base.py +417 -0
  29. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/dom.py +239 -0
  30. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/etree.py +343 -0
  31. tensorbored/_vendor/bleach/_vendor/html5lib/treebuilders/etree_lxml.py +392 -0
  32. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/__init__.py +154 -0
  33. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/base.py +252 -0
  34. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/dom.py +43 -0
  35. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/etree.py +131 -0
  36. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/etree_lxml.py +215 -0
  37. tensorbored/_vendor/bleach/_vendor/html5lib/treewalkers/genshi.py +69 -0
  38. tensorbored/_vendor/bleach/_vendor/parse.py +1078 -0
  39. tensorbored/_vendor/bleach/callbacks.py +32 -0
  40. tensorbored/_vendor/bleach/html5lib_shim.py +757 -0
  41. tensorbored/_vendor/bleach/linkifier.py +633 -0
  42. tensorbored/_vendor/bleach/parse_shim.py +1 -0
  43. tensorbored/_vendor/bleach/sanitizer.py +638 -0
  44. tensorbored/_vendor/bleach/six_shim.py +19 -0
  45. tensorbored/_vendor/webencodings/__init__.py +342 -0
  46. tensorbored/_vendor/webencodings/labels.py +231 -0
  47. tensorbored/_vendor/webencodings/mklabels.py +59 -0
  48. tensorbored/_vendor/webencodings/x_user_defined.py +325 -0
  49. tensorbored/assets.py +36 -0
  50. tensorbored/auth.py +102 -0
  51. tensorbored/backend/__init__.py +0 -0
  52. tensorbored/backend/application.py +604 -0
  53. tensorbored/backend/auth_context_middleware.py +38 -0
  54. tensorbored/backend/client_feature_flags.py +113 -0
  55. tensorbored/backend/empty_path_redirect.py +46 -0
  56. tensorbored/backend/event_processing/__init__.py +0 -0
  57. tensorbored/backend/event_processing/data_ingester.py +276 -0
  58. tensorbored/backend/event_processing/data_provider.py +535 -0
  59. tensorbored/backend/event_processing/directory_loader.py +142 -0
  60. tensorbored/backend/event_processing/directory_watcher.py +272 -0
  61. tensorbored/backend/event_processing/event_accumulator.py +950 -0
  62. tensorbored/backend/event_processing/event_file_inspector.py +463 -0
  63. tensorbored/backend/event_processing/event_file_loader.py +292 -0
  64. tensorbored/backend/event_processing/event_multiplexer.py +521 -0
  65. tensorbored/backend/event_processing/event_util.py +68 -0
  66. tensorbored/backend/event_processing/io_wrapper.py +223 -0
  67. tensorbored/backend/event_processing/plugin_asset_util.py +104 -0
  68. tensorbored/backend/event_processing/plugin_event_accumulator.py +721 -0
  69. tensorbored/backend/event_processing/plugin_event_multiplexer.py +522 -0
  70. tensorbored/backend/event_processing/reservoir.py +266 -0
  71. tensorbored/backend/event_processing/tag_types.py +29 -0
  72. tensorbored/backend/experiment_id.py +71 -0
  73. tensorbored/backend/experimental_plugin.py +51 -0
  74. tensorbored/backend/http_util.py +263 -0
  75. tensorbored/backend/json_util.py +70 -0
  76. tensorbored/backend/path_prefix.py +67 -0
  77. tensorbored/backend/process_graph.py +74 -0
  78. tensorbored/backend/security_validator.py +202 -0
  79. tensorbored/compat/__init__.py +69 -0
  80. tensorbored/compat/proto/__init__.py +0 -0
  81. tensorbored/compat/proto/allocation_description_pb2.py +35 -0
  82. tensorbored/compat/proto/api_def_pb2.py +82 -0
  83. tensorbored/compat/proto/attr_value_pb2.py +80 -0
  84. tensorbored/compat/proto/cluster_pb2.py +58 -0
  85. tensorbored/compat/proto/config_pb2.py +271 -0
  86. tensorbored/compat/proto/coordination_config_pb2.py +45 -0
  87. tensorbored/compat/proto/cost_graph_pb2.py +87 -0
  88. tensorbored/compat/proto/cpp_shape_inference_pb2.py +70 -0
  89. tensorbored/compat/proto/debug_pb2.py +65 -0
  90. tensorbored/compat/proto/event_pb2.py +149 -0
  91. tensorbored/compat/proto/full_type_pb2.py +74 -0
  92. tensorbored/compat/proto/function_pb2.py +157 -0
  93. tensorbored/compat/proto/graph_debug_info_pb2.py +111 -0
  94. tensorbored/compat/proto/graph_pb2.py +41 -0
  95. tensorbored/compat/proto/histogram_pb2.py +39 -0
  96. tensorbored/compat/proto/meta_graph_pb2.py +254 -0
  97. tensorbored/compat/proto/node_def_pb2.py +61 -0
  98. tensorbored/compat/proto/op_def_pb2.py +81 -0
  99. tensorbored/compat/proto/resource_handle_pb2.py +48 -0
  100. tensorbored/compat/proto/rewriter_config_pb2.py +93 -0
  101. tensorbored/compat/proto/rpc_options_pb2.py +35 -0
  102. tensorbored/compat/proto/saved_object_graph_pb2.py +193 -0
  103. tensorbored/compat/proto/saver_pb2.py +38 -0
  104. tensorbored/compat/proto/step_stats_pb2.py +116 -0
  105. tensorbored/compat/proto/struct_pb2.py +144 -0
  106. tensorbored/compat/proto/summary_pb2.py +111 -0
  107. tensorbored/compat/proto/tensor_description_pb2.py +38 -0
  108. tensorbored/compat/proto/tensor_pb2.py +68 -0
  109. tensorbored/compat/proto/tensor_shape_pb2.py +46 -0
  110. tensorbored/compat/proto/tfprof_log_pb2.py +307 -0
  111. tensorbored/compat/proto/trackable_object_graph_pb2.py +90 -0
  112. tensorbored/compat/proto/types_pb2.py +105 -0
  113. tensorbored/compat/proto/variable_pb2.py +62 -0
  114. tensorbored/compat/proto/verifier_config_pb2.py +38 -0
  115. tensorbored/compat/proto/versions_pb2.py +35 -0
  116. tensorbored/compat/tensorflow_stub/__init__.py +38 -0
  117. tensorbored/compat/tensorflow_stub/app.py +124 -0
  118. tensorbored/compat/tensorflow_stub/compat/__init__.py +131 -0
  119. tensorbored/compat/tensorflow_stub/compat/v1/__init__.py +20 -0
  120. tensorbored/compat/tensorflow_stub/dtypes.py +692 -0
  121. tensorbored/compat/tensorflow_stub/error_codes.py +169 -0
  122. tensorbored/compat/tensorflow_stub/errors.py +507 -0
  123. tensorbored/compat/tensorflow_stub/flags.py +124 -0
  124. tensorbored/compat/tensorflow_stub/io/__init__.py +17 -0
  125. tensorbored/compat/tensorflow_stub/io/gfile.py +1011 -0
  126. tensorbored/compat/tensorflow_stub/pywrap_tensorflow.py +285 -0
  127. tensorbored/compat/tensorflow_stub/tensor_shape.py +1035 -0
  128. tensorbored/context.py +129 -0
  129. tensorbored/data/__init__.py +0 -0
  130. tensorbored/data/grpc_provider.py +365 -0
  131. tensorbored/data/ingester.py +46 -0
  132. tensorbored/data/proto/__init__.py +0 -0
  133. tensorbored/data/proto/data_provider_pb2.py +517 -0
  134. tensorbored/data/proto/data_provider_pb2_grpc.py +374 -0
  135. tensorbored/data/provider.py +1365 -0
  136. tensorbored/data/server_ingester.py +301 -0
  137. tensorbored/data_compat.py +159 -0
  138. tensorbored/dataclass_compat.py +224 -0
  139. tensorbored/default.py +124 -0
  140. tensorbored/errors.py +130 -0
  141. tensorbored/lazy.py +99 -0
  142. tensorbored/main.py +48 -0
  143. tensorbored/main_lib.py +62 -0
  144. tensorbored/manager.py +487 -0
  145. tensorbored/notebook.py +441 -0
  146. tensorbored/plugin_util.py +266 -0
  147. tensorbored/plugins/__init__.py +0 -0
  148. tensorbored/plugins/audio/__init__.py +0 -0
  149. tensorbored/plugins/audio/audio_plugin.py +229 -0
  150. tensorbored/plugins/audio/metadata.py +69 -0
  151. tensorbored/plugins/audio/plugin_data_pb2.py +37 -0
  152. tensorbored/plugins/audio/summary.py +230 -0
  153. tensorbored/plugins/audio/summary_v2.py +124 -0
  154. tensorbored/plugins/base_plugin.py +367 -0
  155. tensorbored/plugins/core/__init__.py +0 -0
  156. tensorbored/plugins/core/core_plugin.py +981 -0
  157. tensorbored/plugins/custom_scalar/__init__.py +0 -0
  158. tensorbored/plugins/custom_scalar/custom_scalars_plugin.py +320 -0
  159. tensorbored/plugins/custom_scalar/layout_pb2.py +85 -0
  160. tensorbored/plugins/custom_scalar/metadata.py +35 -0
  161. tensorbored/plugins/custom_scalar/summary.py +79 -0
  162. tensorbored/plugins/debugger_v2/__init__.py +0 -0
  163. tensorbored/plugins/debugger_v2/debug_data_multiplexer.py +631 -0
  164. tensorbored/plugins/debugger_v2/debug_data_provider.py +634 -0
  165. tensorbored/plugins/debugger_v2/debugger_v2_plugin.py +504 -0
  166. tensorbored/plugins/distribution/__init__.py +0 -0
  167. tensorbored/plugins/distribution/compressor.py +158 -0
  168. tensorbored/plugins/distribution/distributions_plugin.py +116 -0
  169. tensorbored/plugins/distribution/metadata.py +19 -0
  170. tensorbored/plugins/graph/__init__.py +0 -0
  171. tensorbored/plugins/graph/graph_util.py +129 -0
  172. tensorbored/plugins/graph/graphs_plugin.py +336 -0
  173. tensorbored/plugins/graph/keras_util.py +328 -0
  174. tensorbored/plugins/graph/metadata.py +42 -0
  175. tensorbored/plugins/histogram/__init__.py +0 -0
  176. tensorbored/plugins/histogram/histograms_plugin.py +144 -0
  177. tensorbored/plugins/histogram/metadata.py +63 -0
  178. tensorbored/plugins/histogram/plugin_data_pb2.py +34 -0
  179. tensorbored/plugins/histogram/summary.py +234 -0
  180. tensorbored/plugins/histogram/summary_v2.py +292 -0
  181. tensorbored/plugins/hparams/__init__.py +14 -0
  182. tensorbored/plugins/hparams/_keras.py +93 -0
  183. tensorbored/plugins/hparams/api.py +130 -0
  184. tensorbored/plugins/hparams/api_pb2.py +208 -0
  185. tensorbored/plugins/hparams/backend_context.py +606 -0
  186. tensorbored/plugins/hparams/download_data.py +158 -0
  187. tensorbored/plugins/hparams/error.py +26 -0
  188. tensorbored/plugins/hparams/get_experiment.py +71 -0
  189. tensorbored/plugins/hparams/hparams_plugin.py +206 -0
  190. tensorbored/plugins/hparams/hparams_util_pb2.py +69 -0
  191. tensorbored/plugins/hparams/json_format_compat.py +38 -0
  192. tensorbored/plugins/hparams/list_metric_evals.py +57 -0
  193. tensorbored/plugins/hparams/list_session_groups.py +1040 -0
  194. tensorbored/plugins/hparams/metadata.py +125 -0
  195. tensorbored/plugins/hparams/metrics.py +41 -0
  196. tensorbored/plugins/hparams/plugin_data_pb2.py +69 -0
  197. tensorbored/plugins/hparams/summary.py +205 -0
  198. tensorbored/plugins/hparams/summary_v2.py +597 -0
  199. tensorbored/plugins/image/__init__.py +0 -0
  200. tensorbored/plugins/image/images_plugin.py +232 -0
  201. tensorbored/plugins/image/metadata.py +65 -0
  202. tensorbored/plugins/image/plugin_data_pb2.py +34 -0
  203. tensorbored/plugins/image/summary.py +159 -0
  204. tensorbored/plugins/image/summary_v2.py +130 -0
  205. tensorbored/plugins/mesh/__init__.py +14 -0
  206. tensorbored/plugins/mesh/mesh_plugin.py +292 -0
  207. tensorbored/plugins/mesh/metadata.py +152 -0
  208. tensorbored/plugins/mesh/plugin_data_pb2.py +37 -0
  209. tensorbored/plugins/mesh/summary.py +251 -0
  210. tensorbored/plugins/mesh/summary_v2.py +214 -0
  211. tensorbored/plugins/metrics/__init__.py +0 -0
  212. tensorbored/plugins/metrics/metadata.py +17 -0
  213. tensorbored/plugins/metrics/metrics_plugin.py +623 -0
  214. tensorbored/plugins/pr_curve/__init__.py +0 -0
  215. tensorbored/plugins/pr_curve/metadata.py +75 -0
  216. tensorbored/plugins/pr_curve/plugin_data_pb2.py +34 -0
  217. tensorbored/plugins/pr_curve/pr_curves_plugin.py +241 -0
  218. tensorbored/plugins/pr_curve/summary.py +574 -0
  219. tensorbored/plugins/profile_redirect/__init__.py +0 -0
  220. tensorbored/plugins/profile_redirect/profile_redirect_plugin.py +49 -0
  221. tensorbored/plugins/projector/__init__.py +67 -0
  222. tensorbored/plugins/projector/metadata.py +26 -0
  223. tensorbored/plugins/projector/projector_config_pb2.py +54 -0
  224. tensorbored/plugins/projector/projector_plugin.py +795 -0
  225. tensorbored/plugins/projector/tf_projector_plugin/index.js +32 -0
  226. tensorbored/plugins/projector/tf_projector_plugin/projector_binary.html +524 -0
  227. tensorbored/plugins/projector/tf_projector_plugin/projector_binary.js +15536 -0
  228. tensorbored/plugins/scalar/__init__.py +0 -0
  229. tensorbored/plugins/scalar/metadata.py +60 -0
  230. tensorbored/plugins/scalar/plugin_data_pb2.py +34 -0
  231. tensorbored/plugins/scalar/scalars_plugin.py +181 -0
  232. tensorbored/plugins/scalar/summary.py +109 -0
  233. tensorbored/plugins/scalar/summary_v2.py +124 -0
  234. tensorbored/plugins/text/__init__.py +0 -0
  235. tensorbored/plugins/text/metadata.py +62 -0
  236. tensorbored/plugins/text/plugin_data_pb2.py +34 -0
  237. tensorbored/plugins/text/summary.py +114 -0
  238. tensorbored/plugins/text/summary_v2.py +124 -0
  239. tensorbored/plugins/text/text_plugin.py +288 -0
  240. tensorbored/plugins/wit_redirect/__init__.py +0 -0
  241. tensorbored/plugins/wit_redirect/wit_redirect_plugin.py +49 -0
  242. tensorbored/program.py +910 -0
  243. tensorbored/summary/__init__.py +35 -0
  244. tensorbored/summary/_output.py +124 -0
  245. tensorbored/summary/_tf/__init__.py +14 -0
  246. tensorbored/summary/_tf/summary/__init__.py +178 -0
  247. tensorbored/summary/_writer.py +105 -0
  248. tensorbored/summary/v1.py +51 -0
  249. tensorbored/summary/v2.py +25 -0
  250. tensorbored/summary/writer/__init__.py +13 -0
  251. tensorbored/summary/writer/event_file_writer.py +291 -0
  252. tensorbored/summary/writer/record_writer.py +50 -0
  253. tensorbored/util/__init__.py +0 -0
  254. tensorbored/util/encoder.py +116 -0
  255. tensorbored/util/grpc_util.py +311 -0
  256. tensorbored/util/img_mime_type_detector.py +40 -0
  257. tensorbored/util/io_util.py +20 -0
  258. tensorbored/util/lazy_tensor_creator.py +110 -0
  259. tensorbored/util/op_evaluator.py +104 -0
  260. tensorbored/util/platform_util.py +20 -0
  261. tensorbored/util/tb_logging.py +24 -0
  262. tensorbored/util/tensor_util.py +617 -0
  263. tensorbored/util/timing.py +122 -0
  264. tensorbored/version.py +21 -0
  265. tensorbored/webfiles.zip +0 -0
  266. tensorbored-2.21.0rc1769983804.dist-info/METADATA +49 -0
  267. tensorbored-2.21.0rc1769983804.dist-info/RECORD +271 -0
  268. tensorbored-2.21.0rc1769983804.dist-info/WHEEL +5 -0
  269. tensorbored-2.21.0rc1769983804.dist-info/entry_points.txt +6 -0
  270. tensorbored-2.21.0rc1769983804.dist-info/licenses/LICENSE +739 -0
  271. tensorbored-2.21.0rc1769983804.dist-info/top_level.txt +1 -0
@@ -0,0 +1,604 @@
1
+ # Copyright 2017 The TensorFlow Authors. All Rights Reserved.
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
+ # ==============================================================================
15
+ """TensorBoard WSGI Application Logic.
16
+
17
+ Provides TensorBoardWSGIApp for building a TensorBoard WSGI app.
18
+ """
19
+
20
+ import base64
21
+ import collections
22
+ import hashlib
23
+ import io
24
+ import json
25
+ import re
26
+ import textwrap
27
+ import time
28
+ from urllib import parse as urlparse
29
+ import zipfile
30
+
31
+ from werkzeug import wrappers
32
+
33
+ from tensorbored import errors
34
+ from tensorbored import plugin_util
35
+ from tensorbored.backend import auth_context_middleware
36
+ from tensorbored.backend import client_feature_flags
37
+ from tensorbored.backend import empty_path_redirect
38
+ from tensorbored.backend import experiment_id
39
+ from tensorbored.backend import experimental_plugin
40
+ from tensorbored.backend import http_util
41
+ from tensorbored.backend import path_prefix
42
+ from tensorbored.backend import security_validator
43
+ from tensorbored.plugins import base_plugin
44
+ from tensorbored.plugins.core import core_plugin
45
+ from tensorbored.util import tb_logging
46
+
47
+ DATA_PREFIX = "/data"
48
+ PLUGIN_PREFIX = "/plugin"
49
+ PLUGINS_LISTING_ROUTE = "/plugins_listing"
50
+ PLUGIN_ENTRY_ROUTE = "/plugin_entry.html"
51
+
52
+ EXPERIMENTAL_PLUGINS_QUERY_PARAM = "experimentalPlugin"
53
+
54
+ # Slashes in a plugin name could throw the router for a loop. An empty
55
+ # name would be confusing, too. To be safe, let's restrict the valid
56
+ # names as follows.
57
+ _VALID_PLUGIN_RE = re.compile(r"^[A-Za-z0-9_-]+$")
58
+
59
+ logger = tb_logging.get_logger()
60
+
61
+
62
+ def TensorBoardWSGIApp(
63
+ flags,
64
+ plugins,
65
+ data_provider=None,
66
+ assets_zip_provider=None,
67
+ deprecated_multiplexer=None,
68
+ auth_providers=None,
69
+ experimental_middlewares=None,
70
+ ):
71
+ """Constructs a TensorBoard WSGI app from plugins and data providers.
72
+
73
+ Args:
74
+ flags: An argparse.Namespace containing TensorBoard CLI flags.
75
+ plugins: A list of plugins, which can be provided as TBPlugin subclasses
76
+ or TBLoader instances or subclasses.
77
+ data_provider: Instance of `tensorboard.data.provider.DataProvider`. May
78
+ be `None` if `flags.generic_data` is set to `"false"` in which case
79
+ `deprecated_multiplexer` must be passed instead.
80
+ assets_zip_provider: See TBContext documentation for more information. If
81
+ `None` a placeholder assets zipfile will be used containing only a
82
+ default `index.html` file, and the actual frontend assets must be
83
+ supplied by middleware wrapping this WSGI app.
84
+ deprecated_multiplexer: Optional `plugin_event_multiplexer.EventMultiplexer`
85
+ to use for any plugins not yet enabled for the DataProvider API.
86
+ Required if the data_provider argument is not passed.
87
+ auth_providers: Optional mapping whose values are `AuthProvider` values
88
+ and whose keys are used by (e.g.) data providers to specify
89
+ `AuthProvider`s via the `AuthContext.get` interface. Defaults to `{}`.
90
+ experimental_middlewares: Optional list of WSGI middlewares (i.e.,
91
+ callables that take a WSGI application and return a WSGI application)
92
+ to apply directly around the core TensorBoard app itself, "inside" the
93
+ request redirection machinery for `--path_prefix`, experiment IDs, etc.
94
+ You can use this to add handlers for additional routes. Middlewares are
95
+ applied in listed order, so the first element of this list is the
96
+ innermost application. Defaults to `[]`. This parameter is experimental
97
+ and may be reworked or removed.
98
+
99
+ Returns:
100
+ A WSGI application that implements the TensorBoard backend.
101
+
102
+ :type plugins: list[base_plugin.TBLoader]
103
+ """
104
+ if assets_zip_provider is None:
105
+ assets_zip_provider = _placeholder_assets_zip_provider
106
+ plugin_name_to_instance = {}
107
+ context = base_plugin.TBContext(
108
+ data_provider=data_provider,
109
+ flags=flags,
110
+ logdir=flags.logdir,
111
+ multiplexer=deprecated_multiplexer,
112
+ assets_zip_provider=assets_zip_provider,
113
+ plugin_name_to_instance=plugin_name_to_instance,
114
+ sampling_hints=flags.samples_per_plugin,
115
+ window_title=flags.window_title,
116
+ )
117
+ tbplugins = []
118
+ experimental_plugins = []
119
+ for plugin_spec in plugins:
120
+ loader = make_plugin_loader(plugin_spec)
121
+ try:
122
+ plugin = loader.load(context)
123
+ except Exception:
124
+ logger.error(
125
+ "Failed to load plugin %s; ignoring it.",
126
+ getattr(loader.load, "__qualname__", loader.load),
127
+ exc_info=True,
128
+ )
129
+ plugin = None
130
+ if plugin is None:
131
+ continue
132
+ tbplugins.append(plugin)
133
+ if isinstance(
134
+ loader, experimental_plugin.ExperimentalPlugin
135
+ ) or isinstance(plugin, experimental_plugin.ExperimentalPlugin):
136
+ experimental_plugins.append(plugin.plugin_name)
137
+ plugin_name_to_instance[plugin.plugin_name] = plugin
138
+ return TensorBoardWSGI(
139
+ tbplugins,
140
+ flags.path_prefix,
141
+ data_provider,
142
+ experimental_plugins,
143
+ auth_providers,
144
+ experimental_middlewares,
145
+ )
146
+
147
+
148
+ def make_plugin_loader(plugin_spec):
149
+ """Returns a plugin loader for the given plugin.
150
+
151
+ Args:
152
+ plugin_spec: A TBPlugin subclass, or a TBLoader instance or subclass.
153
+
154
+ Returns:
155
+ A TBLoader for the given plugin.
156
+
157
+ :type plugin_spec:
158
+ Type[base_plugin.TBPlugin] | Type[base_plugin.TBLoader] |
159
+ base_plugin.TBLoader
160
+ :rtype: base_plugin.TBLoader
161
+ """
162
+ if isinstance(plugin_spec, base_plugin.TBLoader):
163
+ return plugin_spec
164
+ if isinstance(plugin_spec, type):
165
+ if issubclass(plugin_spec, base_plugin.TBLoader):
166
+ return plugin_spec()
167
+ if issubclass(plugin_spec, base_plugin.TBPlugin):
168
+ return base_plugin.BasicLoader(plugin_spec)
169
+ raise TypeError("Not a TBLoader or TBPlugin subclass: %r" % (plugin_spec,))
170
+
171
+
172
+ class TensorBoardWSGI:
173
+ """The TensorBoard WSGI app that delegates to a set of TBPlugin."""
174
+
175
+ def __init__(
176
+ self,
177
+ plugins,
178
+ path_prefix="",
179
+ data_provider=None,
180
+ experimental_plugins=None,
181
+ auth_providers=None,
182
+ experimental_middlewares=None,
183
+ ):
184
+ """Constructs TensorBoardWSGI instance.
185
+
186
+ Args:
187
+ plugins: A list of base_plugin.TBPlugin subclass instances.
188
+ path_prefix: A prefix of the path when app isn't served from root.
189
+ data_provider: `tensorboard.data.provider.DataProvider` or
190
+ `None`; if present, will inform the "active" state of
191
+ `/plugins_listing`.
192
+ experimental_plugins: A list of plugin names that are only provided
193
+ experimentally. The corresponding plugins will only be activated for
194
+ a user if the user has specified the plugin with the experimentalPlugin
195
+ query parameter in the URL.
196
+ auth_providers: Optional mapping whose values are `AuthProvider`
197
+ values and whose keys are used by (e.g.) data providers to specify
198
+ `AuthProvider`s via the `AuthContext.get` interface.
199
+ Defaults to `{}`.
200
+ experimental_middlewares: Optional list of WSGI middlewares to apply
201
+ directly around the core TensorBoard app itself. Defaults to `[]`.
202
+ This parameter is experimental and may be reworked or removed.
203
+
204
+ Returns:
205
+ A WSGI application for the set of all TBPlugin instances.
206
+
207
+ Raises:
208
+ ValueError: If some plugin has no plugin_name
209
+ ValueError: If some plugin has an invalid plugin_name (plugin
210
+ names must only contain [A-Za-z0-9_.-])
211
+ ValueError: If two plugins have the same plugin_name
212
+ ValueError: If some plugin handles a route that does not start
213
+ with a slash
214
+
215
+ :type plugins: list[base_plugin.TBPlugin]
216
+ """
217
+ self._plugins = plugins
218
+ self._path_prefix = path_prefix
219
+ self._data_provider = data_provider
220
+ self._experimental_plugins = frozenset(experimental_plugins or ())
221
+ self._auth_providers = auth_providers or {}
222
+ self._extra_middlewares = list(experimental_middlewares or [])
223
+ if self._path_prefix.endswith("/"):
224
+ # Should have been fixed by `fix_flags`.
225
+ raise ValueError(
226
+ "Trailing slash in path prefix: %r" % self._path_prefix
227
+ )
228
+
229
+ self.exact_routes = {
230
+ # TODO(@chihuahua): Delete this RPC once we have skylark rules that
231
+ # obviate the need for the frontend to determine which plugins are
232
+ # active.
233
+ DATA_PREFIX + PLUGINS_LISTING_ROUTE: self._serve_plugins_listing,
234
+ DATA_PREFIX + PLUGIN_ENTRY_ROUTE: self._serve_plugin_entry,
235
+ }
236
+ unordered_prefix_routes = {}
237
+
238
+ # Serve the routes from the registered plugins using their name as the route
239
+ # prefix. For example if plugin z has two routes /a and /b, they will be
240
+ # served as /data/plugin/z/a and /data/plugin/z/b.
241
+ plugin_names_encountered = set()
242
+ for plugin in self._plugins:
243
+ if plugin.plugin_name is None:
244
+ raise ValueError("Plugin %s has no plugin_name" % plugin)
245
+ if not _VALID_PLUGIN_RE.match(plugin.plugin_name):
246
+ raise ValueError(
247
+ "Plugin %s has invalid name %r"
248
+ % (plugin, plugin.plugin_name)
249
+ )
250
+ if plugin.plugin_name in plugin_names_encountered:
251
+ raise ValueError(
252
+ "Duplicate plugins for name %s" % plugin.plugin_name
253
+ )
254
+ plugin_names_encountered.add(plugin.plugin_name)
255
+
256
+ try:
257
+ plugin_apps = plugin.get_plugin_apps()
258
+ except Exception as e: # pylint: disable=broad-except
259
+ if (
260
+ type(plugin) is core_plugin.CorePlugin
261
+ ): # pylint: disable=unidiomatic-typecheck
262
+ raise
263
+ logger.warning(
264
+ "Plugin %s failed. Exception: %s",
265
+ plugin.plugin_name,
266
+ str(e),
267
+ )
268
+ continue
269
+ for route, app in plugin_apps.items():
270
+ if not route.startswith("/"):
271
+ raise ValueError(
272
+ "Plugin named %r handles invalid route %r: "
273
+ "route does not start with a slash"
274
+ % (plugin.plugin_name, route)
275
+ )
276
+ if (
277
+ type(plugin) is core_plugin.CorePlugin
278
+ ): # pylint: disable=unidiomatic-typecheck
279
+ path = route
280
+ else:
281
+ path = (
282
+ DATA_PREFIX
283
+ + PLUGIN_PREFIX
284
+ + "/"
285
+ + plugin.plugin_name
286
+ + route
287
+ )
288
+
289
+ if path.endswith("/*"):
290
+ # Note we remove the '*' but leave the slash in place.
291
+ path = path[:-1]
292
+ if "*" in path:
293
+ # note we re-add the removed * in the format string
294
+ raise ValueError(
295
+ "Plugin %r handles invalid route '%s*': Only "
296
+ "trailing wildcards are supported "
297
+ "(i.e., `/.../*`)" % (plugin.plugin_name, path)
298
+ )
299
+ unordered_prefix_routes[path] = app
300
+ else:
301
+ if "*" in path:
302
+ raise ValueError(
303
+ "Plugin %r handles invalid route %r: Only "
304
+ "trailing wildcards are supported "
305
+ "(i.e., `/.../*`)" % (plugin.plugin_name, path)
306
+ )
307
+ self.exact_routes[path] = app
308
+
309
+ # Wildcard routes will be checked in the given order, so we sort them
310
+ # longest to shortest so that a more specific route will take precedence
311
+ # over a more general one (e.g., a catchall route `/*` should come last).
312
+ self.prefix_routes = collections.OrderedDict(
313
+ sorted(
314
+ unordered_prefix_routes.items(),
315
+ key=lambda x: len(x[0]),
316
+ reverse=True,
317
+ )
318
+ )
319
+
320
+ self._app = self._create_wsgi_app()
321
+
322
+ def _create_wsgi_app(self):
323
+ """Apply middleware to create the final WSGI app."""
324
+ app = self._route_request
325
+ for middleware in self._extra_middlewares:
326
+ app = middleware(app)
327
+ app = auth_context_middleware.AuthContextMiddleware(
328
+ app, self._auth_providers
329
+ )
330
+ app = client_feature_flags.ClientFeatureFlagsMiddleware(app)
331
+ app = empty_path_redirect.EmptyPathRedirectMiddleware(app)
332
+ app = experiment_id.ExperimentIdMiddleware(app)
333
+ app = path_prefix.PathPrefixMiddleware(app, self._path_prefix)
334
+ app = security_validator.SecurityValidatorMiddleware(app)
335
+ app = _handling_errors(app)
336
+ return app
337
+
338
+ @wrappers.Request.application
339
+ def _serve_plugin_entry(self, request):
340
+ """Serves a HTML for iframed plugin entry point.
341
+
342
+ Args:
343
+ request: The werkzeug.Request object.
344
+
345
+ Returns:
346
+ A werkzeug.Response object.
347
+ """
348
+ name = request.args.get("name")
349
+ plugins = [
350
+ plugin for plugin in self._plugins if plugin.plugin_name == name
351
+ ]
352
+
353
+ if not plugins:
354
+ raise errors.NotFoundError(name)
355
+
356
+ if len(plugins) > 1:
357
+ # Technically is not possible as plugin names are unique and is checked
358
+ # by the check on __init__.
359
+ reason = (
360
+ "Plugin invariant error: multiple plugins with name "
361
+ "{name} found: {list}"
362
+ ).format(name=name, list=plugins)
363
+ raise AssertionError(reason)
364
+
365
+ plugin = plugins[0]
366
+ module_path = plugin.frontend_metadata().es_module_path
367
+ if not module_path:
368
+ return http_util.Respond(
369
+ request, "Plugin is not module loadable", "text/plain", code=400
370
+ )
371
+
372
+ # non-self origin is blocked by CSP but this is a good invariant checking.
373
+ if urlparse.urlparse(module_path).netloc:
374
+ raise ValueError("Expected es_module_path to be non-absolute path")
375
+
376
+ module_json = json.dumps("." + module_path)
377
+ script_content = "import({}).then((m) => void m.render());".format(
378
+ module_json
379
+ )
380
+ digest = hashlib.sha256(script_content.encode("utf-8")).digest()
381
+ script_sha = base64.b64encode(digest).decode("ascii")
382
+
383
+ html = textwrap.dedent(
384
+ """
385
+ <!DOCTYPE html>
386
+ <head><base href="plugin/{name}/" /></head>
387
+ <body><script type="module">{script_content}</script></body>
388
+ """
389
+ ).format(name=name, script_content=script_content)
390
+ return http_util.Respond(
391
+ request,
392
+ html,
393
+ "text/html",
394
+ csp_scripts_sha256s=[script_sha],
395
+ )
396
+
397
+ @wrappers.Request.application
398
+ def _serve_plugins_listing(self, request):
399
+ """Serves an object mapping plugin name to whether it is enabled.
400
+
401
+ Args:
402
+ request: The werkzeug.Request object.
403
+
404
+ Returns:
405
+ A werkzeug.Response object.
406
+ """
407
+ response = collections.OrderedDict()
408
+ ctx = plugin_util.context(request.environ)
409
+ eid = plugin_util.experiment_id(request.environ)
410
+ plugins_with_data = frozenset(
411
+ self._data_provider.list_plugins(ctx, experiment_id=eid)
412
+ or frozenset()
413
+ if self._data_provider is not None
414
+ else frozenset()
415
+ )
416
+ plugins_to_skip = self._experimental_plugins - frozenset(
417
+ request.args.getlist(EXPERIMENTAL_PLUGINS_QUERY_PARAM)
418
+ )
419
+ for plugin in self._plugins:
420
+ if plugin.plugin_name in plugins_to_skip:
421
+ continue
422
+
423
+ if (
424
+ type(plugin) is core_plugin.CorePlugin
425
+ ): # pylint: disable=unidiomatic-typecheck
426
+ # This plugin's existence is a backend implementation detail.
427
+ continue
428
+
429
+ is_active = bool(
430
+ frozenset(plugin.data_plugin_names()) & plugins_with_data
431
+ )
432
+ if not is_active:
433
+ try:
434
+ start = time.time()
435
+ is_active = plugin.is_active()
436
+ elapsed = time.time() - start
437
+ logger.info(
438
+ "Plugin listing: is_active() for %s took %0.3f seconds",
439
+ plugin.plugin_name,
440
+ elapsed,
441
+ )
442
+ except Exception:
443
+ is_active = False
444
+ logger.error(
445
+ "Plugin listing: is_active() for %s failed (marking inactive)",
446
+ plugin.plugin_name,
447
+ exc_info=True,
448
+ )
449
+
450
+ plugin_metadata = plugin.frontend_metadata()
451
+ output_metadata = {
452
+ "disable_reload": plugin_metadata.disable_reload,
453
+ "enabled": is_active,
454
+ # loading_mechanism set below
455
+ "remove_dom": plugin_metadata.remove_dom,
456
+ # tab_name set below
457
+ }
458
+
459
+ if plugin_metadata.tab_name is not None:
460
+ output_metadata["tab_name"] = plugin_metadata.tab_name
461
+ else:
462
+ output_metadata["tab_name"] = plugin.plugin_name
463
+
464
+ es_module_handler = plugin_metadata.es_module_path
465
+ element_name = plugin_metadata.element_name
466
+ is_ng_component = plugin_metadata.is_ng_component
467
+ if is_ng_component:
468
+ if element_name is not None:
469
+ raise ValueError(
470
+ "Plugin %r declared as both Angular built-in and legacy"
471
+ % plugin.plugin_name
472
+ )
473
+ if es_module_handler is not None:
474
+ raise ValueError(
475
+ "Plugin %r declared as both Angular built-in and iframed"
476
+ % plugin.plugin_name
477
+ )
478
+ loading_mechanism = {
479
+ "type": "NG_COMPONENT",
480
+ }
481
+ elif element_name is not None and es_module_handler is not None:
482
+ logger.error(
483
+ "Plugin %r declared as both legacy and iframed; skipping",
484
+ plugin.plugin_name,
485
+ )
486
+ continue
487
+ elif element_name is not None and es_module_handler is None:
488
+ loading_mechanism = {
489
+ "type": "CUSTOM_ELEMENT",
490
+ "element_name": element_name,
491
+ }
492
+ elif element_name is None and es_module_handler is not None:
493
+ loading_mechanism = {
494
+ "type": "IFRAME",
495
+ "module_path": "".join(
496
+ [
497
+ request.script_root,
498
+ DATA_PREFIX,
499
+ PLUGIN_PREFIX,
500
+ "/",
501
+ plugin.plugin_name,
502
+ es_module_handler,
503
+ ]
504
+ ),
505
+ }
506
+ else:
507
+ # As a compatibility measure (for plugins that we don't
508
+ # control), we'll pull it from the frontend registry for now.
509
+ loading_mechanism = {
510
+ "type": "NONE",
511
+ }
512
+ output_metadata["loading_mechanism"] = loading_mechanism
513
+
514
+ response[plugin.plugin_name] = output_metadata
515
+ return http_util.Respond(request, response, "application/json")
516
+
517
+ def __call__(self, environ, start_response):
518
+ """Central entry point for the TensorBoard application.
519
+
520
+ This __call__ method conforms to the WSGI spec, so that instances of this
521
+ class are WSGI applications.
522
+
523
+ Args:
524
+ environ: See WSGI spec (PEP 3333).
525
+ start_response: See WSGI spec (PEP 3333).
526
+ """
527
+ return self._app(environ, start_response)
528
+
529
+ def _route_request(self, environ, start_response):
530
+ """Delegate an incoming request to sub-applications.
531
+
532
+ This method supports strict string matching and wildcard routes of a
533
+ single path component, such as `/foo/*`. Other routing patterns,
534
+ like regular expressions, are not supported.
535
+
536
+ This is the main TensorBoard entry point before middleware is
537
+ applied. (See `_create_wsgi_app`.)
538
+
539
+ Args:
540
+ environ: See WSGI spec (PEP 3333).
541
+ start_response: See WSGI spec (PEP 3333).
542
+ """
543
+
544
+ request = wrappers.Request(environ)
545
+ parsed_url = urlparse.urlparse(request.path)
546
+ clean_path = _clean_path(parsed_url.path)
547
+
548
+ # pylint: disable=too-many-function-args
549
+ if clean_path in self.exact_routes:
550
+ return self.exact_routes[clean_path](environ, start_response)
551
+ else:
552
+ for path_prefix in self.prefix_routes:
553
+ if clean_path.startswith(path_prefix):
554
+ return self.prefix_routes[path_prefix](
555
+ environ, start_response
556
+ )
557
+
558
+ logger.warning("path %s not found, sending 404", clean_path)
559
+ return http_util.Respond(
560
+ request, "Not found", "text/plain", code=404
561
+ )(environ, start_response)
562
+ # pylint: enable=too-many-function-args
563
+
564
+
565
+ def _handling_errors(wsgi_app):
566
+ def wrapper(environ, start_response):
567
+ try:
568
+ return wsgi_app(environ, start_response)
569
+ except errors.PublicError as e:
570
+ request = wrappers.Request(environ)
571
+ error_app = http_util.Respond(
572
+ request,
573
+ str(e),
574
+ "text/plain",
575
+ code=e.http_code,
576
+ headers=e.headers,
577
+ )
578
+ return error_app(environ, start_response)
579
+ # Let other exceptions be handled by the server, as an opaque
580
+ # internal server error.
581
+
582
+ return wrapper
583
+
584
+
585
+ def _clean_path(path):
586
+ """Removes a trailing slash from a non-root path.
587
+
588
+ Arguments:
589
+ path: The path of a request.
590
+
591
+ Returns:
592
+ The route to use to serve the request.
593
+ """
594
+ if path != "/" and path.endswith("/"):
595
+ return path[:-1]
596
+ return path
597
+
598
+
599
+ def _placeholder_assets_zip_provider():
600
+ """Defines a default assets zip provider containing a dummy index.html."""
601
+ memfile = io.BytesIO()
602
+ with zipfile.ZipFile(memfile, mode="w") as zf:
603
+ zf.writestr("index.html", "TensorBoard placeholder index.html")
604
+ return memfile
@@ -0,0 +1,38 @@
1
+ # Copyright 2023 The TensorFlow Authors. All Rights Reserved.
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
+ # ==============================================================================
15
+ from tensorbored import auth
16
+ from tensorbored import context
17
+
18
+
19
+ class AuthContextMiddleware:
20
+ """WSGI middleware to inject an AuthContext into the RequestContext."""
21
+
22
+ def __init__(self, application, auth_providers):
23
+ """Initializes this middleware.
24
+
25
+ Args:
26
+ application: A WSGI application to delegate to.
27
+ auth_providers: The auth_providers to provide to the AuthContext.
28
+ """
29
+ self._application = application
30
+ self._auth_providers = auth_providers
31
+
32
+ def __call__(self, environ, start_response):
33
+ """Invoke this WSGI application."""
34
+ environ = dict(environ)
35
+ auth_ctx = auth.AuthContext(self._auth_providers, environ)
36
+ ctx = context.from_environ(environ).replace(auth=auth_ctx)
37
+ context.set_in_environ(environ, ctx)
38
+ return self._application(environ, start_response)