Skip to content

Commit 957f4fb

Browse files
Merged in cbillington/lyse/feature (pull request labscript-suite#22)
Unicode strings and other Python 3 forward-compatible changes. Approved-by: Jan Werkmann <jan.wrk.fb@gmail.com>
2 parents 184e629 + 5badebd commit 957f4fb

6 files changed

+136
-26
lines changed

__init__.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@
1111
# #
1212
#####################################################################
1313

14-
from dataframe_utilities import get_series_from_shot as _get_singleshot
15-
from dataframe_utilities import dict_diff
14+
from __future__ import division, unicode_literals, print_function, absolute_import
15+
16+
from lyse.dataframe_utilities import get_series_from_shot as _get_singleshot, dict_diff
1617
import os
17-
import urllib
18-
import urllib2
1918
import socket
2019
import pickle as pickle
2120
import inspect
@@ -38,7 +37,11 @@
3837

3938
# require pandas v0.15.0 up to the next major version
4039
check_version('pandas', '0.15.0', '1.0')
41-
check_version('zprocess', '2.2', '3.0')
40+
check_version('zprocess', '2.2.0', '3.0')
41+
check_version('labscript_utils', '2.4', '3.0')
42+
from labscript_utils import PY2
43+
if PY2:
44+
str = unicode
4245

4346
# If running stand-alone, and not from within lyse, the below two variables
4447
# will be as follows. Otherwise lyse will override them with spinning_top =
@@ -114,6 +117,9 @@ def __init__(self,h5_path,no_write=False):
114117
# this Run object:
115118
frame = inspect.currentframe()
116119
__file__ = frame.f_back.f_locals['__file__']
120+
if PY2:
121+
__file__ = __file__.decode(sys.getfilesystemencoding())
122+
print(repr(__file__))
117123
self.group = os.path.basename(__file__).split('.py')[0]
118124
with h5py.File(h5_path) as h5_file:
119125
if not self.group in h5_file['results']:
@@ -224,7 +230,7 @@ def save_results(self, *args):
224230
names = args[::2]
225231
values = args[1::2]
226232
for name, value in zip(names, values):
227-
print 'saving %s ='%name, value
233+
print('saving %s =' % name, value)
228234
self.save_result(name, value)
229235

230236
def save_results_dict(self, results_dict, uncertainties=False, **kwargs):
@@ -372,6 +378,8 @@ def __init__(self,h5_path,run_paths):
372378
frame = inspect.currentframe()
373379
try:
374380
__file__ = frame.f_back.f_locals['__file__']
381+
if PY2:
382+
__file__ = __file__.decode(sys.getfilesystemencoding())
375383
self.group = os.path.basename(__file__).split('.py')[0]
376384
with h5py.File(h5_path) as h5_file:
377385
if not self.group in h5_file['results']:

__main__.py

+22-14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import os
66
import sys
77
import socket
8-
import Queue
98
import logging
109
import threading
1110
import signal
@@ -31,9 +30,7 @@
3130
except ImportError:
3231
raise ImportError('Require labscript_utils > 2.1.0')
3332

34-
check_version('labscript_utils', '2.1', '3.0')
3533
check_version('qtutils', '2.0.0', '3.0.0')
36-
check_version('zprocess', '1.1.7', '3.0')
3734

3835
import zprocess.locking
3936
from zprocess import ZMQServer
@@ -54,6 +51,13 @@
5451
from qtutils.auto_scroll_to_end import set_auto_scroll_to_end
5552
import qtutils.icons
5653

54+
from labscript_utils import PY2
55+
if PY2:
56+
str = unicode
57+
import Queue as queue
58+
else:
59+
import queue
60+
5761
# Set working directory to lyse folder, resolving symlinks
5862
lyse_dir = os.path.dirname(os.path.realpath(__file__))
5963
os.chdir(lyse_dir)
@@ -878,10 +882,14 @@ def populate_model(self, column_names, columns_visible):
878882
self.ui.treeView.resizeColumnToContents(self.COL_VISIBLE)
879883
# Which indices in self.columns_visible the row numbers correspond to
880884
self.column_indices = {}
881-
for column_index, name in sorted(column_names.items(), key=lambda s: s[1]):
882-
if not isinstance(name, tuple):
883-
# one of our special columns, ignore:
884-
continue
885+
886+
# Remove our special columns from the dict of column names by keeping only tuples:
887+
column_names = {i: name for i, name in column_names.items() if isinstance(name, tuple)}
888+
889+
# Sort the column names as comma separated values, converting to lower case:
890+
sortkey = lambda item: ', '.join(item[1]).lower().strip(', ')
891+
892+
for column_index, name in sorted(column_names.items(), key=sortkey):
885893
visible = columns_visible[column_index]
886894
visible_item = QtGui.QStandardItem()
887895
visible_item.setCheckable(True)
@@ -893,7 +901,7 @@ def populate_model(self, column_names, columns_visible):
893901
visible_item.setData(QtCore.Qt.Unchecked, self.ROLE_SORT_DATA)
894902
name_as_string = ', '.join(name).strip(', ')
895903
name_item = QtGui.QStandardItem(name_as_string)
896-
name_item.setData(name_as_string, self.ROLE_SORT_DATA)
904+
name_item.setData(sortkey((column_index, name)), self.ROLE_SORT_DATA)
897905
self.model.appendRow([visible_item, name_item])
898906
self.column_indices[self.model.rowCount() - 1] = column_index
899907

@@ -1578,7 +1586,7 @@ def __init__(self, container, exp_config, to_singleshot, from_singleshot, to_mul
15781586
# A queue for storing incoming files from the ZMQ server so
15791587
# the server can keep receiving files even if analysis is slow
15801588
# or paused:
1581-
self.incoming_queue = Queue.Queue()
1589+
self.incoming_queue = queue.Queue()
15821590

15831591
# Start the thread to handle incoming files, and store them in
15841592
# a buffer if processing is paused:
@@ -1686,7 +1694,7 @@ def incoming_buffer_loop(self):
16861694
while True:
16871695
try:
16881696
filepath = self.incoming_queue.get(False)
1689-
except Queue.Empty:
1697+
except queue.Empty:
16901698
break
16911699
else:
16921700
filepaths.append(filepath)
@@ -1851,12 +1859,12 @@ def __init__(self):
18511859

18521860
# The singleshot routinebox will be connected to the filebox
18531861
# by queues:
1854-
to_singleshot = Queue.Queue()
1855-
from_singleshot = Queue.Queue()
1862+
to_singleshot = queue.Queue()
1863+
from_singleshot = queue.Queue()
18561864

18571865
# So will the multishot routinebox:
1858-
to_multishot = Queue.Queue()
1859-
from_multishot = Queue.Queue()
1866+
to_multishot = queue.Queue()
1867+
from_multishot = queue.Queue()
18601868

18611869
self.output_box = OutputBox(self.ui.verticalLayout_output_box)
18621870
self.singleshot_routinebox = RoutineBox(self.ui.verticalLayout_singleshot_routinebox, self.exp_config,

analysis_subprocess.py

+41-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
# #
1212
#####################################################################
1313

14+
from __future__ import division, unicode_literals, print_function, absolute_import
15+
from labscript_utils import PY2
16+
if PY2:
17+
str = unicode
18+
1419
import labscript_utils.excepthook
1520
import zprocess
1621
to_parent, from_parent, kill_lock = zprocess.setup_connection_with_parent(lock = True)
@@ -221,9 +226,18 @@ def __init__(self, filepath, to_parent, from_parent):
221226
self.to_parent = to_parent
222227
self.from_parent = from_parent
223228
self.filepath = filepath
229+
230+
# Filepath as a unicode string on py3 and a bytestring on py2,
231+
# so that the right string type can be passed to functions that
232+
# require the 'native' string type for that python version. On
233+
# Python 2, encode it with the filesystem encoding.
234+
if PY2:
235+
self.filepath_native_string = self.filepath.encode(sys.getfilesystemencoding())
236+
else:
237+
self.filepath_native_string = self.filepath
224238

225239
# Add user script directory to the pythonpath:
226-
sys.path.insert(0, os.path.dirname(self.filepath))
240+
sys.path.insert(0, os.path.dirname(self.filepath_native_string))
227241

228242
# Plot objects, keyed by matplotlib Figure object:
229243
self.plots = {}
@@ -272,7 +286,7 @@ def do_analysis(self, path):
272286
# The namespace the routine will run in:
273287
sandbox = _DeprecationDict(path=path,
274288
__name__='__main__',
275-
__file__= self.filepath)
289+
__file__= os.path.basename(self.filepath_native_string))
276290
# path global variable is deprecated:
277291
deprecation_message = ("use of 'path' global variable is deprecated and will be removed " +
278292
"in a future version of lyse. Please use lyse.path, which defaults " +
@@ -281,20 +295,43 @@ def do_analysis(self, path):
281295
# Use lyse.path instead:
282296
lyse.path = path
283297
lyse._updated_data = {}
298+
299+
# Save the current working directory before changing it to the
300+
# location of the user's script:
301+
cwd = os.getcwd()
302+
os.chdir(os.path.dirname(self.filepath))
303+
284304
# Do not let the modulewatcher unload any modules whilst we're working:
285305
try:
286306
with self.modulewatcher.lock:
287307
# Actually run the user's analysis!
288-
execfile(self.filepath, sandbox, sandbox)
308+
with open(self.filepath) as f:
309+
code = compile(f.read(), os.path.basename(self.filepath_native_string),
310+
'exec', dont_inherit=True)
311+
exec(code, sandbox)
289312
except:
290313
traceback_lines = traceback.format_exception(*sys.exc_info())
291314
del traceback_lines[1]
292-
message = ''.join(traceback_lines)
315+
# Avoiding a list comprehension here so as to avoid this
316+
# python bug in earlier versions of 2.7 (fixed in 2.7.9):
317+
# https://bugs.python.org/issue21591
318+
message = ''
319+
for line in traceback_lines:
320+
if PY2:
321+
# errors='replace' is for Windows filenames present in the
322+
# traceback that are not UTF8. They will not display
323+
# correctly, but that's the best we can do - the traceback
324+
# may contain code from the file in a different encoding,
325+
# so we could have a mixed encoding string. This is only
326+
# a problem for Python 2.
327+
line = line.decode('utf8', errors='replace')
328+
message += line
293329
sys.stderr.write(message)
294330
return False
295331
else:
296332
return True
297333
finally:
334+
os.chdir(cwd)
298335
print('')
299336
self.post_analysis_plot_actions()
300337

dataframe_utilities.py

+53-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
# #
1212
#####################################################################
1313

14+
from __future__ import division, unicode_literals, print_function, absolute_import
15+
from labscript_utils import PY2
16+
if PY2:
17+
str = unicode
18+
1419
import labscript_utils.h5_lock, h5py
1520
import pandas
1621
import os
@@ -20,7 +25,53 @@
2025

2126
import runmanager
2227

28+
# Monkey patch a bugfix onto older versions of pandas on Python 2. This code
29+
# can be removed once lyse otherwise depends on pandas >= 0.21.0.
30+
# https://github.com/pandas-dev/pandas/pull/17099
31+
if PY2:
32+
try:
33+
from labscript_utils import check_version, VersionException
34+
check_version('pandas', '0.21.0', '2.0')
35+
except VersionException:
36+
37+
import numpy as np
38+
from pandas import Series, Index
39+
from pandas.core.indexing import maybe_droplevels
40+
def _getitem_multilevel(self, key):
41+
loc = self.columns.get_loc(key)
42+
if isinstance(loc, (slice, Series, np.ndarray, Index)):
43+
new_columns = self.columns[loc]
44+
result_columns = maybe_droplevels(new_columns, key)
45+
if self._is_mixed_type:
46+
result = self.reindex(columns=new_columns)
47+
result.columns = result_columns
48+
else:
49+
new_values = self.values[:, loc]
50+
result = self._constructor(new_values, index=self.index,
51+
columns=result_columns)
52+
result = result.__finalize__(self)
53+
if len(result.columns) == 1:
54+
top = result.columns[0]
55+
if isinstance(top, tuple):
56+
top = top[0]
57+
if top == '':
58+
result = result['']
59+
if isinstance(result, Series):
60+
result = self._constructor_sliced(result,
61+
index=self.index,
62+
name=key)
63+
64+
result._set_is_copy(self)
65+
return result
66+
else:
67+
return self._get_item_cache(key)
68+
69+
pandas.DataFrame._getitem_multilevel = _getitem_multilevel
70+
71+
2372
def asdatetime(timestr):
73+
if isinstance(timestr, bytes):
74+
timestr = timestr.decode('utf-8')
2475
tz = tzlocal.get_localzone().zone
2576
return pandas.Timestamp(timestr, tz=tz)
2677

@@ -78,10 +129,10 @@ def flatten_dict(dictionary, keys=tuple()):
78129
result = {}
79130
for name in dictionary:
80131
if isinstance(dictionary[name],dict):
81-
flat = flatten_dict(dictionary[name],keys=keys + (str(name),))
132+
flat = flatten_dict(dictionary[name],keys=keys + (name,))
82133
result.update(flat)
83134
else:
84-
result[keys + (str(name),)] = dictionary[name]
135+
result[keys + (name,)] = dictionary[name]
85136
return result
86137

87138
def flat_dict_to_hierarchical_dataframe(dictionary):

figure_manager.py

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
# #
1212
#####################################################################
1313

14+
from __future__ import division, unicode_literals, print_function, absolute_import
15+
from labscript_utils import PY2
16+
if PY2:
17+
str = unicode
18+
1419
import lyse
1520
import sys
1621

tempfile2clipboard.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# #
1212
#####################################################################
1313

14+
from __future__ import division, unicode_literals, print_function, absolute_import
1415
import sys
1516
import os
1617

0 commit comments

Comments
 (0)