~launchpad-p-s/sofastatistics/main

« back to all changes in this revision

Viewing changes to util.py

  • Committer: Grant Paton-Simpson
  • Date: 2009-05-19 04:21:43 UTC
  • Revision ID: g@ubuntu-20090519042143-p561mbokz3inefvd
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
import datetime
 
3
import os
 
4
import sys
 
5
import time
 
6
import wx
 
7
 
 
8
def in_windows():
 
9
    try:
 
10
        in_windows = os.environ['OS'].lower().startswith("windows")
 
11
    except Exception:
 
12
        in_windows = False
 
13
    return in_windows
 
14
 
 
15
def get_user_paths():
 
16
    if in_windows():
 
17
        user_path = os.path.join(os.environ['HOMEDRIVE'], 
 
18
                                 os.environ['HOMEPATH'])
 
19
    else:
 
20
        user_path = os.environ['HOME']
 
21
    local_path = os.path.join(user_path, "sofa")
 
22
    return user_path, local_path
 
23
 
 
24
def get_prog_path():
 
25
    if in_windows():
 
26
        prog_path = os.environ['PROGRAMFILES']
 
27
    else:
 
28
        prog_path = os.path.join("usr", "share", "pyshared")
 
29
    return prog_path
 
30
 
 
31
class StaticWrapText(wx.StaticText):
 
32
    """
 
33
    A StaticText-like widget which implements word wrapping.
 
34
    http://yergler.net/projects/stext/stext_py.txt
 
35
    __id__ = "$Id: stext.py,v 1.1 2004/09/15 16:45:55 nyergler Exp $"
 
36
    __version__ = "$Revision: 1.1 $"
 
37
    __copyright__ = '(c) 2004, Nathan R. Yergler'
 
38
    __license__ = 'licensed under the GNU GPL2'
 
39
    """
 
40
    
 
41
    def __init__(self, *args, **kwargs):
 
42
        wx.StaticText.__init__(self, *args, **kwargs)
 
43
        # store the initial label
 
44
        self.__label = super(StaticWrapText, self).GetLabel()
 
45
        # listen for sizing events
 
46
        self.Bind(wx.EVT_SIZE, self.OnSize)
 
47
 
 
48
    def SetLabel(self, newLabel):
 
49
        """Store the new label and recalculate the wrapped version."""
 
50
        self.__label = newLabel
 
51
        self.__wrap()
 
52
 
 
53
    def GetLabel(self):
 
54
        """Returns the label (unwrapped)."""
 
55
        return self.__label
 
56
    
 
57
    def __wrap(self):
 
58
        """Wraps the words in label."""
 
59
        words = self.__label.split()
 
60
        lines = []
 
61
        # get the maximum width (that of our parent)
 
62
        max_width = self.GetParent().GetVirtualSizeTuple()[0]        
 
63
        index = 0
 
64
        current = []
 
65
        for word in words:
 
66
            current.append(word)
 
67
            if self.GetTextExtent(" ".join(current))[0] > max_width:
 
68
                del current[-1]
 
69
                lines.append(" ".join(current))
 
70
                current = [word]
 
71
        # pick up the last line of text
 
72
        lines.append(" ".join(current))
 
73
        # set the actual label property to the wrapped version
 
74
        super(StaticWrapText, self).SetLabel("\n".join(lines))
 
75
        # refresh the widget
 
76
        self.Refresh()
 
77
        
 
78
    def OnSize(self, event):
 
79
        # dispatch to the wrap method which will 
 
80
        # determine if any changes are needed
 
81
        self.__wrap()
 
82
 
 
83
def get_script_path():
 
84
    """
 
85
    NB won't work within an interpreter
 
86
    """
 
87
    return sys.path[0]
 
88
 
 
89
def get_local_path():
 
90
    return "%s/sofa" % os.getenv('HOME')
 
91
 
 
92
def isInteger(val): #http://mail.python.org/pipermail/python-list/2006-February/368113.html
 
93
    return isinstance(val, (int, long))
 
94
 
 
95
def isNumeric(val):
 
96
    "http://www.rosettacode.org/wiki/IsNumeric#Python"
 
97
    try:
 
98
      i = float(val)
 
99
    except ValueError:
 
100
        return False
 
101
    else:
 
102
        return True
 
103
 
 
104
def flip_date(date):
 
105
    """Reorder MySQL date e.g. 2008-11-23 -> 23-11-2008"""
 
106
    return "%s-%s-%s" % (date[-2:], date[5:7], date[:4])
 
107
 
 
108
def date_range2mysql(entered_start_date, entered_end_date):
 
109
    """
 
110
    Takes date range in format "01-01-2008" for both start and end
 
111
        and returns start_date and end_date in mysql format.
 
112
    """
 
113
    #start date and end date must both be a valid date in that format
 
114
    try:
 
115
        #valid formats: http://docs.python.org/lib/module-time.html
 
116
        time.strptime(entered_start_date, "%d-%m-%Y")
 
117
        time.strptime(entered_end_date, "%d-%m-%Y")
 
118
        def DDMMYYYY2MySQL(value):
 
119
            #e.g. 26-04-2001 -> 2001-04-26 less flexible than using strptime and strftime together but much quicker
 
120
            return "%s-%s-%s" % (value[-4:], value[3:5], value[:2]) #NB slice points, not start and length
 
121
        start_date = DDMMYYYY2MySQL(entered_start_date)#now make MySQL-friendly dates
 
122
        end_date = DDMMYYYY2MySQL(entered_end_date)#now make MySQL-friendly dates
 
123
        return start_date, end_date     
 
124
    except:
 
125
        raise Exception, "Please pass valid start and " + \
 
126
            "end dates as per the required format e.g. 25-01-2007"    
 
127
 
 
128
def mysql2textdate(mysql_date, output_format):
 
129
    """
 
130
    Takes MySQL date e.g. 2008-01-25 and returns date string according to 
 
131
        format.  NB must be valid format for strftime.
 
132
    """
 
133
    year = int(mysql_date[:4])
 
134
    month = int(mysql_date[5:7])
 
135
    day = int(mysql_date[-2:])
 
136
    mydate = (year, month, day, 0, 0, 0, 0, 0, 0)
 
137
    return time.strftime(output_format, mydate)
 
138
 
 
139
def is_date_part(datetime_str):
 
140
    """
 
141
    Assumes date will have - or / and time will not.
 
142
    If a mishmash will fail ok_date later.
 
143
    """
 
144
    return "-" in datetime_str or "/" in datetime_str
 
145
 
 
146
def is_time_part(datetime_str):
 
147
    """
 
148
    Assumes time will have : (or am/pm) and date will not.
 
149
    If a mishmash will fail ok_time later.
 
150
    """
 
151
    return ":" in datetime_str or "am" in datetime_str.lower() \
 
152
        or "pm" in datetime_str.lower()
 
153
 
 
154
def IsYear(datetime_str):
 
155
    is_year = False
 
156
    try:
 
157
        year = int(datetime_str)
 
158
        is_year = (1900 <= year < 10000) 
 
159
    except Exception:
 
160
        pass
 
161
    return is_year
 
162
 
 
163
def datetime_split(datetime_str):
 
164
    """
 
165
    Split date and time (if both)
 
166
    Return date part, time part, order (True unless order 
 
167
        time then date).
 
168
    Return None for any missing components.
 
169
    Must be only one space in string (if any) - between date and time
 
170
        (or time and date).
 
171
    """
 
172
    if " " in datetime_str:
 
173
        datetime_split = datetime_str.split(" ")
 
174
        if len(datetime_split) != 2:
 
175
            return (None, None, True)
 
176
        if is_date_part(datetime_split[0]) \
 
177
                and is_time_part(datetime_split[1]):
 
178
            return (datetime_split[0], datetime_split[1], True)
 
179
        elif is_date_part(datetime_split[1]) \
 
180
                and is_time_part(datetime_split[0]):
 
181
            return (datetime_split[1], datetime_split[0], False)
 
182
        else:
 
183
            return (None, None, date_then_time)
 
184
    elif IsYear(datetime_str):
 
185
        return (datetime_str, None, True)
 
186
    else: # only one part
 
187
        date_then_time = True
 
188
        if is_date_part(datetime_str):
 
189
            return (datetime_str, None, date_then_time)
 
190
        elif is_time_part(datetime_str):
 
191
            return (None, datetime_str, date_then_time)
 
192
        else:
 
193
            return (None, None, date_then_time)
 
194
 
 
195
def datetime_str_valid(datetime_str):
 
196
    """
 
197
    Is the datetime string in a valid format?
 
198
    Returns tuple of Boolean and either a time object if True 
 
199
        or None if False. 
 
200
    Used for checking user-entered datetimes.
 
201
    Doesn't cover all possibilities - just what is needed for typical data 
 
202
        entry.
 
203
    If only a time, use today's date.
 
204
    If only a date, use midnight as time e.g. MySQL 00:00:00
 
205
    Acceptable formats for date component are:
 
206
    2009, 2008-02-26, 1-1-2008, 01-01-2008, 01/01/2008, 1/1/2008.
 
207
    Acceptable formats for time are:
 
208
    2pm, 2:30pm, 14:30 , 14:30:00
 
209
    http://docs.python.org/library/datetime.html#module-datetime
 
210
    Should only be one space in string (if any) - between date and time
 
211
        (or time and date).
 
212
    """
 
213
    debug = False
 
214
    # evaluate date and/or time components against allowable formats
 
215
    date_part, time_part, date_time_order = datetime_split(datetime_str)
 
216
    if date_part == None and time_part == None:
 
217
        return False, None
 
218
    # gather information on all parts
 
219
    if date_part:
 
220
        ok_date = False
 
221
        ok_date_format = None
 
222
        ok_date_formats = ["%d-%m-%Y", "%d/%m/%Y", "%Y-%m-%d", "%Y"]
 
223
        for ok_date_format in ok_date_formats:
 
224
            try:
 
225
                t = time.strptime(date_part, ok_date_format)
 
226
                ok_date = True
 
227
                break
 
228
            except:
 
229
                pass
 
230
    if time_part:
 
231
        ok_time = False
 
232
        ok_time_format = None
 
233
        ok_time_formats = ["%I%p", "%I:%M%p", "%H:%M", "%H:%M:%S"]
 
234
        for ok_time_format in ok_time_formats:
 
235
            try:
 
236
                t = time.strptime(time_part, ok_time_format)
 
237
                ok_time = True
 
238
                break
 
239
            except:
 
240
                pass
 
241
    # determine what type of valid datetime then make time object    
 
242
    if date_part != None and time_part != None and ok_date and ok_time:
 
243
        if date_time_order: # date then time            
 
244
            t = time.strptime("%s %s" % (date_part, time_part), 
 
245
                              "%s %s" % (ok_date_format, ok_time_format))
 
246
        else: # time then date
 
247
            t = time.strptime("%s %s" % (time_part, date_part),
 
248
                              "%s %s" % (ok_time_format, ok_date_format))
 
249
    elif date_part != None and time_part == None and ok_date:
 
250
        # date only (add time of 00:00:00)
 
251
        t = time.strptime("%s 00:00:00" % date_part, 
 
252
                          "%s %%H:%%M:%%S" % ok_date_format)
 
253
    elif date_part == None and time_part != None and ok_time:
 
254
        # time only (assume today's date)
 
255
        today = time.localtime()
 
256
        t = time.strptime("%s-%s-%s %s" % (today[0], today[1], today[2], 
 
257
                                           time_part), 
 
258
                          "%%Y-%%m-%%d %s" % ok_time_format)
 
259
    else:
 
260
        return False, None
 
261
    if debug: print t
 
262
    return True, t
 
263
 
 
264
def get_time_taken(t1, t2):
 
265
    """Return hours, minutes, seconds of time taken between t1 and t2.
 
266
    Use time.clock() to create t1 and t2 inputs"""
 
267
    #http://pleac.sourceforge.net/pleac_python/datesandtimes.html
 
268
    difference  = t2 - t1
 
269
    minutes, seconds = divmod(difference, 60)
 
270
    hours, minutes = divmod(minutes, 60)
 
271
    time_taken_dic = {'hours': int(hours), 'minutes': int(minutes), 'seconds': int(seconds)}
 
272
    return time_taken_dic
 
273
 
 
274
def get_datetime_stamp():
 
275
    """Get datetime stamp as string e.g. 2008-06-30-16-45-03"""
 
276
    now = datetime.datetime.now() #http://pleac.sourceforge.net/pleac_python/datesandtimes.html
 
277
    datetimestamp = now.strftime("%Y-%m-%d-%H-%M-%S")
 
278
    return datetimestamp
 
279
 
 
280
def getBytesUnit(intbytes):
 
281
    """
 
282
    Takes raw number of bytes and returns best string representation 
 
283
    e.g. 0.4 MB
 
284
    """
 
285
    if intbytes < 1024:
 
286
        return "%s bytes" % intbytes
 
287
    elif intbytes < 1024*500:
 
288
        return "%f KB" % (intbytes/1024.0)
 
289
    elif intbytes < 1024*1024*500:
 
290
        return "%.2f MB" % (intbytes/(1024.0*1024))
 
291
    else:
 
292
        return "%.2f GB" % (intbytes/(1024.0*1024*1024)) 
 
293
 
 
294
def getText(parent, title, message, default=""):
 
295
    "Get user text"
 
296
    dlg = wx.TextEntryDialog(parent, message, title, default, 
 
297
                             style=wx.OK|wx.CANCEL)
 
298
    if dlg.ShowModal() == wx.ID_OK:
 
299
        text = dlg.GetValue()
 
300
    else:
 
301
        text = None
 
302
    dlg.Destroy()
 
303
    return text
 
304
    
 
305
def getTreeCtrlChildren(tree, parent):
 
306
    "Get children of TreeCtrl item"
 
307
    children = []
 
308
    item, cookie = tree.GetFirstChild(parent) #p.471 wxPython
 
309
    while item:
 
310
        children.append(item)
 
311
        item, cookie = tree.GetNextChild(parent, cookie)
 
312
    return children
 
313
 
 
314
def ItemHasChildren(tree, parent):
 
315
    """
 
316
    tree.ItemHasChildren(item_id) doesn't work if root is hidden.
 
317
    E.g. self.tree = wx.gizmos.TreeListCtrl(self, -1, 
 
318
                      style=wx.TR_FULL_ROW_HIGHLIGHT | \
 
319
                      wx.TR_HIDE_ROOT)
 
320
    wxTreeCtrl::ItemHasChildren
 
321
    bool ItemHasChildren(const wxTreeItemId& item) const
 
322
    Returns TRUE if the item has children.
 
323
    """
 
324
    item, cookie = tree.GetFirstChild(parent)
 
325
    return True if item else False
 
326
 
 
327
def getTreeCtrlDescendants(tree, parent, descendants=None):
 
328
    """
 
329
    Get all descendants (descendent is an alternative spelling 
 
330
    in English grrr).
 
331
    """
 
332
    if descendants == None:
 
333
        descendants = []
 
334
    children = getTreeCtrlChildren(tree, parent)
 
335
    for child in children:
 
336
        descendants.append(child)
 
337
        getTreeCtrlDescendants(tree, child, descendants)
 
338
    return descendants
 
339
 
 
340
def getSubTreeItems(tree, parent):
 
341
    "Return string representing subtree"
 
342
    descendants = getTreeCtrlDescendants(tree, parent)
 
343
    descendant_labels = [tree.GetItemText(x) for x in descendants]
 
344
    return ", ".join(descendant_labels)
 
345
 
 
346
def getTreeAncestors(tree, child):
 
347
    "Get ancestors of TreeCtrl item"
 
348
    ancestors = []
 
349
    item = tree.GetItemParent(child)
 
350
    while item:
 
351
        ancestors.append(item)
 
352
        item = tree.GetItemParent(item)
 
353
    return ancestors
 
354
 
 
355
def if_none(value, default):
 
356
    "Returns default if value is None - otherwise returns value"
 
357
    if value == None:
 
358
        return default
 
359
    else:
 
360
        return value
 
361
 
 
362
def MySQL2string(value):
 
363
    """
 
364
    Takes field data in MySQL and processes for output.  
 
365
    In particular, handles dates.
 
366
    """
 
367
    import time
 
368
    if value == None:
 
369
        return ""
 
370
    elif type(value).__name__ == 'datetime': #http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/511451
 
371
        date_portion = str(value)[0:10]
 
372
        #Unless you put a "'" on the front, Calc treats older dates (about pre-1947) as strings not dates :-(
 
373
        #formats are in http://docs.python.org/lib/module-time.html
 
374
        return time.strftime("%Y-%m-%d", time.strptime(date_portion, "%Y-%m-%d")) #unless YYY-mm-dd output, spreadsheets often misinterpret dates (US format issue probably)
 
375
    else:
 
376
        return str(value)
 
377
    
 
378
def run_mysql_cmd(cmd, DB_HOST, DB_USER, DB_PWD):
 
379
    "Run a MySQL command as a subprocess that will not work using the cursor"
 
380
    import subprocess
 
381
    args = "\"C:\\Program Files\\MySQL\\MySQL Server 5.0\\bin\\mysql.exe\" " + \
 
382
        " -h%s -u%s -p%s " % (DB_HOST, DB_USER, DB_PWD) + \
 
383
        " --database=pgftransfer %s" % (cmd)
 
384
    child = subprocess.Popen(args=args, shell=True, executable="C:\\windows\\system32\\cmd.exe")
 
385
    
 
386
def get_new_id(start_value=0):
 
387
    """
 
388
    Get next number in sequence - uses iterator rather than global
 
389
    http://www.daniweb.com/forums/thread33025.html
 
390
    NB if repeatedly define this generator, will be fresh each time.  Define once,
 
391
    and call multiple times for the desired result ;-)
 
392
    """
 
393
    id = start_value
 
394
    while True:
 
395
        id += 1
 
396
        yield id
 
397
    
 
398
def empty_or_none_to_null(value, quote=False):
 
399
    """
 
400
    Change empty strings and None to NULL or return string version of value.
 
401
    Can return string value single quoted for inclusion into SQL so that 
 
402
    either NULL or 'value' is returned as appropriate e.g. when running inserts.
 
403
    """
 
404
    if value == None:
 
405
        return "NULL"
 
406
    elif value == "":
 
407
        return "NULL"
 
408
    else:
 
409
        if quote:
 
410
            return "'" + str(value) + "'"
 
411
        else:
 
412
            return str(value)