22
22
23
23
psutil = get_module ('psutil' )
24
24
25
+ from _plotly_future_ import _future_flags
26
+
25
27
# Valid image format constants
26
28
# ----------------------------
27
29
valid_formats = ('png' , 'jpeg' , 'webp' , 'svg' , 'pdf' , 'eps' )
@@ -417,7 +419,11 @@ def executable(self):
417
419
-------
418
420
str
419
421
"""
420
- return self ._props .get ('executable' , 'orca' )
422
+ executable_list = self ._props .get ('executable_list' , ['orca' ])
423
+ if executable_list is None :
424
+ return None
425
+ else :
426
+ return ' ' .join (executable_list )
421
427
422
428
@executable .setter
423
429
def executable (self , val ):
@@ -429,7 +435,9 @@ def executable(self, val):
429
435
raise ValueError ("""
430
436
The executable property must be a string, but received value of type {typ}.
431
437
Received value: {val}""" .format (typ = type (val ), val = val ))
432
- self ._props ['executable' ] = val
438
+ if isinstance (val , string_types ):
439
+ val = [val ]
440
+ self ._props ['executable_list' ] = val
433
441
434
442
# Server and validation must restart before setting is active
435
443
reset_status ()
@@ -661,6 +669,28 @@ def mapbox_access_token(self, val):
661
669
# Server must restart before setting is active
662
670
shutdown_server ()
663
671
672
+ @property
673
+ def use_xvfb (self ):
674
+ dflt = 'auto' if 'orca_defaults' in _future_flags else False
675
+ return self ._props .get ('use_xvfb' , dflt )
676
+
677
+ @use_xvfb .setter
678
+ def use_xvfb (self , val ):
679
+ valid_vals = [True , False , 'auto' ]
680
+ if val is None :
681
+ self ._props .pop ('use_xvfb' , None )
682
+ else :
683
+ if val not in valid_vals :
684
+ raise ValueError ("""
685
+ The use_xvfb property must be one of {valid_vals}
686
+ Received value of type {typ}: {val}""" .format (
687
+ valid_vals = valid_vals , typ = type (val ), val = repr (val )))
688
+
689
+ self ._props ['use_xvfb' ] = val
690
+
691
+ # Server and validation must restart before setting is active
692
+ reset_status ()
693
+
664
694
@property
665
695
def plotlyjs (self ):
666
696
"""
@@ -704,6 +734,7 @@ def __repr__(self):
704
734
mathjax: {mathjax}
705
735
topojson: {topojson}
706
736
mapbox_access_token: {mapbox_access_token}
737
+ use_xvfb: {use_xvfb}
707
738
708
739
constants
709
740
---------
@@ -721,7 +752,8 @@ def __repr__(self):
721
752
topojson = self .topojson ,
722
753
mapbox_access_token = self .mapbox_access_token ,
723
754
plotlyjs = self .plotlyjs ,
724
- config_file = self .config_file )
755
+ config_file = self .config_file ,
756
+ use_xvfb = self .use_xvfb )
725
757
726
758
727
759
# Make config a singleton object
@@ -738,7 +770,7 @@ class OrcaStatus(object):
738
770
"""
739
771
_props = {
740
772
'state' : 'unvalidated' , # or 'validated' or 'running'
741
- 'executable ' : None ,
773
+ 'executable_list ' : None ,
742
774
'version' : None ,
743
775
'pid' : None ,
744
776
'port' : None ,
@@ -770,7 +802,11 @@ def executable(self):
770
802
771
803
This property will be None if the `state` is 'unvalidated'.
772
804
"""
773
- return self ._props ['executable' ]
805
+ executable_list = self ._props ['executable_list' ]
806
+ if executable_list is None :
807
+ return None
808
+ else :
809
+ return ' ' .join (executable_list )
774
810
775
811
@property
776
812
def version (self ):
@@ -851,7 +887,11 @@ def orca_env():
851
887
to orca is transformed into a call to nodejs.
852
888
See https://github.com/plotly/orca/issues/149#issuecomment-443506732
853
889
"""
854
- clear_env_vars = ['NODE_OPTIONS' , 'ELECTRON_RUN_AS_NODE' ]
890
+ clear_env_vars = [
891
+ 'NODE_OPTIONS' ,
892
+ 'ELECTRON_RUN_AS_NODE' ,
893
+ 'LD_PRELOAD'
894
+ ]
855
895
orig_env_vars = {}
856
896
857
897
try :
@@ -932,11 +972,10 @@ def validate_executable():
932
972
# -------------------------
933
973
# Search for executable name or path in config.executable
934
974
executable = which (config .executable )
975
+ path = os .environ .get ("PATH" , os .defpath )
976
+ formatted_path = path .replace (os .pathsep , '\n ' )
935
977
936
978
if executable is None :
937
- path = os .environ .get ("PATH" , os .defpath )
938
- formatted_path = path .replace (os .pathsep , '\n ' )
939
-
940
979
raise ValueError ("""
941
980
The orca executable is required to export figures as static images,
942
981
but it could not be found on the system path.
@@ -949,6 +988,37 @@ def validate_executable():
949
988
formatted_path = formatted_path ,
950
989
instructions = install_location_instructions ))
951
990
991
+ # Check if we should run with Xvfb
992
+ # --------------------------------
993
+ xvfb_args = ["--auto-servernum" ,
994
+ "--server-args" ,
995
+ "-screen 0 640x480x24 +extension RANDR +extension GLX" ,
996
+ executable ]
997
+
998
+ if config .use_xvfb == True :
999
+ # Use xvfb
1000
+ xvfb_run_executable = which ('xvfb-run' )
1001
+ if not xvfb_run_executable :
1002
+ raise ValueError ("""
1003
+ The plotly.io.orca.config.use_xvfb property is set to True, but the
1004
+ xvfb-run executable could not be found on the system path.
1005
+
1006
+ Searched for the executable 'xvfb-run' on the following path:
1007
+ {formatted_path}""" .format (formatted_path = formatted_path ))
1008
+
1009
+ executable_list = [xvfb_run_executable ] + xvfb_args
1010
+ elif (config .use_xvfb == 'auto' and
1011
+ sys .platform .startswith ('linux' ) and
1012
+ not os .environ .get ('DISPLAY' ) and
1013
+ which ('xvfb-run' )):
1014
+ # use_xvfb is 'auto', we're on linux without a display server,
1015
+ # and xvfb-run is available. Use it.
1016
+ xvfb_run_executable = which ('xvfb-run' )
1017
+ executable_list = [xvfb_run_executable ] + xvfb_args
1018
+ else :
1019
+ # Do not use xvfb
1020
+ executable_list = [executable ]
1021
+
952
1022
# Run executable with --help and see if it's our orca
953
1023
# ---------------------------------------------------
954
1024
invalid_executable_msg = """
@@ -964,7 +1034,7 @@ def validate_executable():
964
1034
# ### Run with Popen so we get access to stdout and stderr
965
1035
with orca_env ():
966
1036
p = subprocess .Popen (
967
- [ executable , '--help' ],
1037
+ executable_list + [ '--help' ],
968
1038
stdout = subprocess .PIPE ,
969
1039
stderr = subprocess .PIPE )
970
1040
@@ -977,7 +1047,7 @@ def validate_executable():
977
1047
978
1048
[Return code: {returncode}]
979
1049
{err_msg}
980
- """ .format (executable = executable ,
1050
+ """ .format (executable = ' ' . join ( executable_list ) ,
981
1051
err_msg = help_error .decode ('utf-8' ),
982
1052
returncode = p .returncode )
983
1053
@@ -987,17 +1057,25 @@ def validate_executable():
987
1057
988
1058
err_msg += """\
989
1059
Note: When used on Linux, orca requires an X11 display server, but none was
990
- detected. Please install X11, or configure your system with Xvfb. See
991
- the orca README (https://github.com/plotly/orca) for instructions on using
992
- orca with Xvfb.
1060
+ detected. Please install Xvfb and configure plotly.py to run orca using Xvfb
1061
+ as follows:
1062
+
1063
+ >>> import plotly.io as pio
1064
+ >>> pio.orca.config.use_xvfb = True
1065
+
1066
+ You can save this configuration for use in future sessions as follows:
1067
+ >>> pio.orca.config.save()
1068
+
1069
+ See https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml
1070
+ for more info on Xvfb
993
1071
"""
994
1072
raise ValueError (err_msg )
995
1073
996
1074
if not help_result :
997
1075
raise ValueError (invalid_executable_msg + """
998
1076
The error encountered is that no output was returned by the command
999
1077
$ {executable} --help
1000
- """ .format (executable = executable ))
1078
+ """ .format (executable = ' ' . join ( executable_list ) ))
1001
1079
1002
1080
if ("Plotly's image-exporting utilities" not in
1003
1081
help_result .decode ('utf-8' )):
@@ -1006,14 +1084,14 @@ def validate_executable():
1006
1084
$ {executable} --help
1007
1085
1008
1086
{help_result}
1009
- """ .format (executable = executable , help_result = help_result ))
1087
+ """ .format (executable = ' ' . join ( executable_list ) , help_result = help_result ))
1010
1088
1011
1089
# Get orca version
1012
1090
# ----------------
1013
1091
# ### Run with Popen so we get access to stdout and stderr
1014
1092
with orca_env ():
1015
1093
p = subprocess .Popen (
1016
- [ executable , '--version' ],
1094
+ executable_list + [ '--version' ],
1017
1095
stdout = subprocess .PIPE ,
1018
1096
stderr = subprocess .PIPE )
1019
1097
@@ -1029,7 +1107,7 @@ def validate_executable():
1029
1107
1030
1108
[Return code: {returncode}]
1031
1109
{err_msg}
1032
- """ .format (executable = executable ,
1110
+ """ .format (executable = ' ' . join ( executable_list ) ,
1033
1111
err_msg = version_error .decode ('utf-8' ),
1034
1112
returncode = p .returncode ))
1035
1113
@@ -1039,11 +1117,11 @@ def validate_executable():
1039
1117
Here is the command that plotly.py ran to request the version:
1040
1118
1041
1119
$ {executable} --version
1042
- """ .format (executable = executable ))
1120
+ """ .format (executable = ' ' . join ( executable_list ) ))
1043
1121
else :
1044
1122
version_result = version_result .decode ()
1045
1123
1046
- status ._props ['executable ' ] = executable
1124
+ status ._props ['executable_list ' ] = executable_list
1047
1125
status ._props ['version' ] = version_result .strip ()
1048
1126
status ._props ['state' ] = 'validated'
1049
1127
@@ -1061,7 +1139,7 @@ def reset_status():
1061
1139
None
1062
1140
"""
1063
1141
shutdown_server ()
1064
- status ._props ['executable ' ] = None
1142
+ status ._props ['executable_list ' ] = None
1065
1143
status ._props ['version' ] = None
1066
1144
status ._props ['state' ] = 'unvalidated'
1067
1145
@@ -1179,10 +1257,11 @@ def ensure_server():
1179
1257
orca_state ['port' ] = config .port
1180
1258
1181
1259
# Build orca command list
1182
- cmd_list = [status .executable , 'serve' ,
1183
- '-p' , str (orca_state ['port' ]),
1184
- '--plotly' , config .plotlyjs ,
1185
- '--graph-only' ]
1260
+ cmd_list = status ._props ['executable_list' ] + [
1261
+ 'serve' ,
1262
+ '-p' , str (orca_state ['port' ]),
1263
+ '--plotly' , config .plotlyjs ,
1264
+ '--graph-only' ]
1186
1265
1187
1266
if config .topojson :
1188
1267
cmd_list .extend (['--topojson' , config .topojson ])
@@ -1198,8 +1277,9 @@ def ensure_server():
1198
1277
# specified port.
1199
1278
DEVNULL = open (os .devnull , 'wb' )
1200
1279
with orca_env ():
1201
- orca_state ['proc' ] = subprocess .Popen (cmd_list ,
1202
- stdout = DEVNULL )
1280
+ orca_state ['proc' ] = subprocess .Popen (
1281
+ cmd_list , stdout = DEVNULL
1282
+ )
1203
1283
1204
1284
# Update orca.status so the user has an accurate view
1205
1285
# of the state of the orca server
0 commit comments