3
from parser_data import SUMMABLE_FIELDS
5
# Our basic module for interaction with our nutritional information DB
9
"""Handle all interactions with our nutrition database.
11
We provide methods to set up equivalences between our
12
ingredient-keys and our nutritional data.
15
def __init__ (self, db, conv):
18
self.conv.density_table
19
self.gramwght_regexp = re.compile("([0-9.]+)?( ?([^,]+))?(, (.*))?")
20
self.wght_breaker = re.compile('([^ ,]+)([, ]+\(?(.*)\)?)?$')
22
def set_key (self, key, row):
23
"""Create an automatic equivalence for ingredient key 'key' and nutritional DB row ROW
25
if not row: row = self._get_key(key)
26
#density=self.get_density(key,row)
27
if row: self.row.ndbno=row.ndbno
29
self.db.do_add(self.db.nutritionaliases_table,
33
def set_density_for_key (self, key, density_equivalent):
35
self.db.nutritionaliases_table,
37
{'density_equivalent':density_equivalent}
40
def set_key_from_ndbno (self, key, ndbno):
41
"""Create an automatic equivalence between ingredient key 'key' and ndbno
42
ndbno is our nutritional database number."""
45
prev_association = self.db.fetch_one(self.db.nutritionaliases_table,ingkey=key)
47
self.db.do_modify(self.db.nutritionaliases_table,
51
self.db.do_add(self.db.nutritionaliases_table,{'ndbno':ndbno,
55
def set_conversion (self, key, unit, factor):
56
"""Set conversion for ingredient key.
58
factor is the amount we multiply by to get from unit to grams.
60
if self.conv.unit_dict.has_key(unit):
61
unit = self.conv.unit_dict[unit]
62
prev_entry = self.db.fetch_one(self.db.nutritionconversions_table,
63
**{'ingkey':key,'unit':unit})
65
self.db.do_modify(self.db.nutritionconversions_table,
69
self.db.do_add(self.db.nutritionconversions_table,{'ingkey':key,'unit':unit,'factor':factor})
71
def get_matches (self, key, max=50):
72
"""Handed a string, get a list of likely USDA database matches.
74
We return a list of lists:
75
[[description, nutritional-database-number],...]
77
If max is not none, we cut our list off at max items (and hope our
78
sorting algorithm succeeded in picking out the good matches!).
80
words=re.split("\W",key)
81
words = filter(lambda w: w and not w in ['in','or','and','with'], words)
83
result = self.db.search_nutrition(words)
84
while not result and len(words)>1:
86
result = self.db.search_nutrition(words)
88
return [(r.desc,r.ndbno) for r in result]
92
def _get_key (self, key):
93
"""Handed an ingredient key, get our nutritional Database equivalent
95
row=self.db.fetch_one(self.db.nutritionaliases_table,**{'ingkey':str(key)})
98
def get_nutinfo_for_ing (self, ing, rd=None):
99
"""A convenience function that grabs the requisite items from
102
As a side effect we set the ingObject attribute on the object we return
105
if hasattr(ing,'refid') and ing.refid:
106
subrec = rd.get_referenced_rec(ing)
107
nutinfo = self.get_nutinfo_for_inglist(rd.get_ings(subrec),rd)
108
nutinfo.ingObject = ing
109
print '(recref) Set nutinfo.ingObject to ',ing
111
if hasattr(ing,'rangeamount') and ing.rangeamount:
112
# just average our amounts
113
amount = (ing.rangeamount + ing.amount)/2
116
if not amount: amount=1
117
nutinfo = self.get_nutinfo_for_item(ing.ingkey,amount,ing.unit)
118
print 'Setting ingObject to',ing
119
nutinfo.ingObject = ing
122
def get_nutinfo_for_inglist (self, inglist, rd):
123
"""A convenience function to get NutritionInfoList for a list of
126
print 'get_nutinfo_for_inglist',inglist,rd
127
return NutritionInfoList([self.get_nutinfo_for_ing(i,rd) for i in inglist])
129
def get_nutinfo_for_item (self, key, amt, unit):
130
"""Handed a key, amount and unit, get out nutritional Database object.
132
ni=self.get_nutinfo(key)
136
c=self.get_conversion_for_amt(amt,unit,key)
138
return NutritionInfo(ni,mult=c)
139
return NutritionVapor(self,key,
144
def get_nutinfo (self, key):
145
"""Get our nutritional information for ingredient key 'key'
146
We return an object interfacing with our DB whose attributes
147
will be nutritional values.
149
aliasrow = self._get_key(key)
151
nvrow=self.db.fetch_one(self.db.nutrition_table,**{'ndbno':aliasrow.ndbno})
152
if nvrow: return NutritionInfo(nvrow)
153
# if we don't have a nutritional db row, return a
154
# NutritionVapor instance which remembers our query and allows
155
# us to redo it. The idea here is that our callers will get
156
# an object that can guarantee them the latest nutritional
157
# information for a given item.
158
return NutritionVapor(self,key)
160
def get_ndbno (self, key):
161
aliasrow = self._get_key(key)
162
if aliasrow: return aliasrow.ndbno
165
def convert_to_grams (self, amt, unit, key, row=None):
166
conv = self.get_conversion_for_amt(amt,unit,key,row)
167
if conv: return conv*100
171
def get_conversion_for_amt (self, amt, unit, key, row=None, fudge=True):
172
"""Get a conversion for amount amt of unit 'unit' to USDA standard.
174
Multiplying our standard numbers (/100g) will get us the appropriate
177
get_conversion_for_amt(amt,unit,key) * 100 will give us the
178
number of grams this AMOUNT converts to.
180
# our default is 100g
181
cnv=self.conv.converter('g',unit)
182
if not row: row=self.get_nutinfo(key)
184
cnv = self.conv.converter('g',unit,
185
density=self.get_density(key,row,fudge=fudge)
188
# Check our weights tables...
189
extra_conversions = self.get_conversions(key,row)[1]
190
if extra_conversions.has_key(unit):
191
cnv = extra_conversions[unit]
192
elif unit and extra_conversions.has_key(unit.lower()):
193
cnv = extra_conversions[unit.lower()]
195
# lookup in our custom nutrition-related conversion table
196
if self.conv.unit_dict.has_key(unit):
197
unit = self.conv.unit_dict[unit]
200
lookup = self.db.fetch_one(self.db.nutritionconversions_table,ingkey=key,unit=unit)
204
# otherwise, cycle through any units we have and see
205
# if we can get a conversion via those units...
206
for conv in self.db.fetch_all(self.db.nutritionconversions_table,ingkey=key):
207
factor = self.conv.converter(unit,conv.unit)
209
cnv = conv.factor*factor
211
return (0.01*amt)/cnv
213
def get_conversions (self, key=None, row=None):
214
"""Handed an ingredient key or a row of the nutrition database,
215
we return two dictionaries, one with Unit Conversions and the other
216
with densities. Our return dictionaries look like this:
217
({'chopped':1.03, #density dic
220
'leg':48,} # unit : grams
222
if not row: row=self.get_nutinfo(key)
223
if not row: return {},{}
226
for gd,gw in self.get_gramweights(row).items():
229
convfactor = self.conv.converter(u,'ml')
230
if convfactor: #if we are a volume
231
# divide mass by volume converted to mililiters
232
# (since gramwts are in grams!)
233
density = float(gw) / (a * convfactor)
236
# if we can't get a density from this amount, we're going to treat it as a unit!
237
if e: u = u + ", " + e
238
if a: gw = float(gw)/a
242
return densities,units
244
def get_densities (self,key=None,row=None):
245
"""Handed key or nutrow, return dictionary with densities."""
246
if not row: row = self._get_key(key)
247
if not row: return {}
248
if self.conv.density_table.has_key(key):
249
return {'':self.conv.density_table[key]}
252
for gd,gw in self.get_gramweights(row).items():
256
convfactor=self.conv.converter(u,'ml')
257
if convfactor: # if we are a volume
258
# divide mass by volume converted to milileters
259
# (gramwts are in grams)
260
density = float(gw) / (a * convfactor)
264
def get_gramweights (self,row):
265
"""Return a dictionary with gram weights.
268
nutweights = self.db.fetch_all(self.db.usda_weights_table,**{'ndbno':row.ndbno})
269
for nw in nutweights:
270
mtch = self.wght_breaker.match(nw.unit)
275
unit = mtch.groups()[0]
276
extra = mtch.groups()[2]
277
ret[(nw.amount,unit,extra)]=nw.gramwt
280
def get_density (self,key=None,row=None, fudge=True):
281
densities = self.get_densities(key,row)
282
if densities.has_key(''): densities[None]=densities['']
283
if key: keyrow=self._get_key(key)
285
if key and keyrow.density_equivalent and densities.has_key(keyrow.density_equivalent):
286
return densities[keyrow.density_equivalent]
287
elif densities.has_key(None):
288
self.conv.density_table[key]=densities[None]
289
return densities[None]
290
elif len(densities)==1:
291
return densities.values()[0]
293
return sum(densities.values())/len(densities)
297
def parse_gramweight_measure (self, txt):
298
m=self.gramwght_regexp.match(txt)
302
if amt: amt = float(amt)
305
return amt,unit,extra
307
def add_custom_nutrition_info (self, nutrition_dictionary):
308
"""Add custom nutritional information."""
309
#new_ndbno = self.db.increment_field(self.db.nutrition_table,'ndbno')
310
#if new_ndbno: nutrition_dictionary['ndbno']=new_ndbno
311
return self.db.do_add_nutrition(nutrition_dictionary).ndbno
315
"""A multipliable way to reference an object.
317
Any attribute of object that can be mutiplied, will be returned
320
We can also support various mathematical operators
321
n = NutritionInfo(obj, mult=2)
322
n * 2 -> NutritionInfo(obj,mult=4)
323
n2 = NutritionInfo(obj2, mult=3)
324
n2 + n -> NutritionInfoList([n2,n])
326
The result is that addition and multiplication 'makes sense' for
327
properties. For example, if we have nutrition info for 1 carrot,
328
we can multiply it or add it to the nutrition info for an
329
eggplant. The resulting object will reflect the appropriate
332
Carrot = NutritionInfo(CarrotNutritionRow)
333
Eggplant = NutritionInfo(EggplantNutritionRow)
337
(Carrot + Eggplant).kcal => 65
338
(Carrot * 3 + Eggplant).kcal => 147
340
This will be true for all numeric properties.
342
Non numeric properties return a somewhat not-useful string:
344
(Carrot + Eggplant).desc => 'CARROTS,RAW, EGGPLANT,RAW'
346
def __init__ (self,rowref, mult=1, fudged=False):
347
self.__rowref__ = rowref
349
self.__fudged__ = fudged
351
def __getattr__ (self, attr):
353
ret = getattr(self.__rowref__, attr)
355
if attr in SUMMABLE_FIELDS:
356
return (ret or 0) * self.__mult__
362
# somehow this magically gets us standard
363
# attribute handling...
364
raise AttributeError, attr
366
def __add__ (self, obj):
367
if isinstance(obj,NutritionInfo):
368
return NutritionInfoList([self,obj])
369
elif isinstance(obj,NutritionInfoList):
370
return NutritionInfoList([self]+obj.__nutinfos__)
372
def __mul__ (self, n):
373
return NutritionInfo(self.__rowref__, self.__mult__ * n)
375
KEY_VAPOR = 0 # when we don't have a key
376
UNIT_VAPOR = 1 # when we can't interpret the unit
377
DENSITY_VAPOR = 2 # when we don't have a density
378
AMOUNT_VAPOR = 3 # when there is no amount, leaving us quite confused
380
class NutritionVapor (NutritionInfo):
381
"""An object to hold our nutritional information before we know it.
383
Basically, we have to behave like a NutritionInfo class that doesn't
384
actually return any data.
386
We also can return information about why we're still vapor
387
(whether we need density info, key info or what...).
389
def __init__ (self, nd, key,
395
self.__rowref__ = rowref
398
self.__amt__ = amount
402
"""Try to create matter from vapor and return it.
404
If we fail we return more vapor."""
405
if not self.__rowref__:
407
ni = self.__nd__.get_nutinfo(self.__key__)
408
if not isinstance(ni,NutritionVapor): return ni * self.__mult__
411
return self.__nd__.get_nutinfo_for_item(self.__key__,
415
c=self.__nd__.get_conversion_for_amt(self.__amt__,self.__unit__,self.__key__,fudge=False)
418
return NutritionInfo(self.__rowref__,
421
c=self.__nd__.get_conversion_for_amt(self.__amt__,self.__unit__,self.__key__,fudge=True)
424
return NutritionInfo(self.__rowref__,
428
else: return self.__nd__.get_nutinfo_for_item(self.__key__,self.__amt__,self.__unit__)
430
def __getattr__ (self,attr):
431
"""Return 0 for any requests for a non _ prefixed attribute."""
435
raise AttributeError,attr
438
return '<NutritionVapor %s>'%self.__key__
440
def __nonzero__ (self):
441
"""Vapor is always False."""
444
def _wheres_the_vapor (self):
445
"""Return a key as to why we're vapor."""
446
if not self.__rowref__: return KEY_VAPOR
447
elif not self.__amt__: return AMOUNT_VAPOR
448
else: return UNIT_VAPOR
450
class NutritionInfoList (list, NutritionInfo):
451
"""A summable list of objects.
453
When we ask for numeric attributes of our members, we get the sum.
455
def __init__ (self,nutinfos, mult=1):
456
self.__nutinfos__ = nutinfos
457
#self.__len__ = self.__nutinfos__.__len__
458
#self.__getitem__ = self.__nutinfos__.__getitem__
461
def __getattr__ (self, attr):
463
alist = [getattr(ni,attr) for ni in self.__nutinfos__]
464
if attr in SUMMABLE_FIELDS:
465
if self.__mult__: alist = [n * self.__mult__ for n in alist]
468
return ", ".join(map(str,alist))
470
# somehow this magically gets us standard
471
# attribute handling...
472
raise AttributeError, attr
475
"""See if we can turn any of our vapor into matter."""
476
for i in range(len(self.__nutinfos__)):
477
obj = self.__nutinfos__[i]
478
if isinstance(obj,NutritionVapor):
480
self.__nutinfos__[i]=obj._reset()
482
def _get_vapor (self):
483
"""Return a list of nutritionVapor if there is any
485
In other words, tell us whether we are missing any nutritional
488
for i in self.__nutinfos__:
489
if isinstance(i,NutritionVapor): ret.append(i)
490
if isinstance(i,NutritionInfoList):
491
ret.extend(i._get_vapor())
494
def _get_fudge (self):
495
"""Return a list of fudged items
498
for i in self.__nutinfos__:
499
if hasattr(i,'__fudged__') and i.__fudged__:
503
def __add__ (self, obj):
504
if isinstance(obj,NutritionInfo):
505
return NutritionInfoList(self.__nutinfos__ + [obj])
506
elif isinstance(obj,NutritionInfoList):
507
return NutritionInfoList(self.__nutinfos__ + obj.__nutinfos__)
509
def __sub__ (self, obj):
510
copy = self.__nutinfos__[0:]
512
return NutritionInfoList(copy)
514
def __getslice__ (self, a, b):
515
return NutritionInfoList(self.__nutinfos__[a:b])
517
def __len__ (self): return len(self.__nutinfos__)
518
def __getitem__ (self,x): return self.__nutinfos__[x]
521
return '<NutritionInfoList>'
523
def recursive_length (self):
524
"""Return number of contained nutrition info objects, recursing any embedded lists.
527
for x in range(len(self)):
529
if isinstance(obj,NutritionInfoList):
530
n += obj.recursive_length()
535
if __name__ == '__main__':
537
sys.path.append('/usr/share/')
538
import gourmet.recipeManager as rm
539
db=rm.RecipeManager(**rm.dbargs)
540
import gourmet.convert
541
conv = gourmet.convert.converter()
542
import nutritionGrabberGui
543
nutritionGrabberGui.check_for_db(db)
544
nd=NutritionData(db,conv)
547
from gourmet import convert
548
class SimpleInterface:
550
def __init__ (self, nd):
551
self.ACTIONS = {'Add ingredient':self.add_ingredient,
552
'Add key info':self.add_key,
553
'Print info':self.print_info,
560
choices = self.ACTIONS.keys()
561
for n,a in enumerate(choices):
565
choice = raw_input('Enter number of choice: ')
567
if choice < len(choices): choice = self.ACTIONS[choices[choice]]
577
def add_ingredient (self):
578
key=raw_input('Enter ingredient key: ')
579
amt = convert.frac_to_float(raw_input('Enter amount: '))
580
unit = raw_input('Enter unit: ')
582
self.ings = NutritionInfoList([self.nd.get_nutinfo_for_item(key,amt,unit)])
584
self.ings = self.ings + self.nd.get_nutinfo_for_item(key,amt,unit)
587
key=raw_input('Enter key for which we add info: ')
588
matches = self.nd.get_matches(key,10)
589
for n,m in enumerate(matches):
593
choice = raw_input('Enter number of choice: ')
595
if choice < len(matches): choice = matches[choice][1]
597
self.nd.set_key_from_ndbno(key,choice)
600
def print_info (self):
601
att = raw_input('What information would you like (e.g. kcal): ')
602
while not hasattr(self.ings,att):
603
print "I'm sorry, there is no information about ",att
604
att = raw_input('What information would you like (e.g. kcal): ')
605
print att,":",getattr(self.ings,att)
606
vv = self.ings._get_vapor()
608
print '(but we have some vapor)'
610
explanation = v._wheres_the_vapor()
611
print 'Vapor for ',v.__key__
612
if explanation==KEY_VAPOR: print 'No key'
613
if explanation==UNIT_VAPOR: print "Can't handle unit ",v.__unit__
614
if explanation==AMOUNT_VAPOR: print "What am I to do with the amount ",v.__amt__
620
si = SimpleInterface(nd)
624
#while raw_input('Get another density?'):
625
# row=random.choice(db.nutrition_table)
626
# print 'Information: ',row.desc, nd.get_conversions(row=row)
627
# #print 'Gramweights: ',nd.get_gramweights(row)
628
# #print 'Density of ',row.desc,' = ',nd.get_densities(row)