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):
34
self.db.update_by_criteria(
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):
99
"""A convenience function that grabs the requisite items from
101
if hasattr(ing,'refid') and ing.refid:
102
subrec = rd.get_referenced_rec(ing)
103
return self.get_nutinfo_for_inglist(rd.get_ings(subrec),rd,ingObject=ing)
104
if hasattr(ing,'rangeamount') and ing.rangeamount:
105
# just average our amounts
106
amount = (ing.rangeamount + ing.amount)/2
109
if not amount: amount=1
110
return self.get_nutinfo_for_item(ing.ingkey,amount,ing.unit,ingObject=ing)
112
def get_nutinfo_for_inglist (self, inglist, rd, ingObject=None):
113
"""A convenience function to get NutritionInfoList for a list of
116
return NutritionInfoList([self.get_nutinfo_for_ing(i,rd) for i in inglist],
119
def get_nutinfo_for_item (self, key, amt, unit, ingObject=None):
120
"""Handed a key, amount and unit, get out nutritional Database object.
122
ni=self.get_nutinfo(key)
126
c=self.get_conversion_for_amt(amt,unit,key)
128
return NutritionInfo(ni,mult=c,ingObject=ingObject)
129
return NutritionVapor(self,key,
135
def get_nutinfo (self, key):
136
"""Get our nutritional information for ingredient key 'key'
137
We return an object interfacing with our DB whose attributes
138
will be nutritional values.
140
aliasrow = self._get_key(key)
142
nvrow=self.db.fetch_one(self.db.nutrition_table,**{'ndbno':aliasrow.ndbno})
143
if nvrow: return NutritionInfo(nvrow)
144
# if we don't have a nutritional db row, return a
145
# NutritionVapor instance which remembers our query and allows
146
# us to redo it. The idea here is that our callers will get
147
# an object that can guarantee them the latest nutritional
148
# information for a given item.
149
return NutritionVapor(self,key)
151
def get_ndbno (self, key):
152
aliasrow = self._get_key(key)
153
if aliasrow: return aliasrow.ndbno
156
def convert_to_grams (self, amt, unit, key, row=None):
157
conv = self.get_conversion_for_amt(amt,unit,key,row)
158
if conv: return conv*100
162
def get_conversion_for_amt (self, amt, unit, key, row=None, fudge=True):
163
"""Get a conversion for amount amt of unit 'unit' to USDA standard.
165
Multiplying our standard numbers (/100g) will get us the appropriate
168
get_conversion_for_amt(amt,unit,key) * 100 will give us the
169
number of grams this AMOUNT converts to.
171
# our default is 100g
172
cnv=self.conv.converter('g',unit)
173
if not row: row=self.get_nutinfo(key)
175
cnv = self.conv.converter('g',unit,
176
density=self.get_density(key,row,fudge=fudge)
179
# Check our weights tables...
180
extra_conversions = self.get_conversions(key,row)[1]
181
if extra_conversions.has_key(unit):
182
cnv = extra_conversions[unit]
183
elif unit and extra_conversions.has_key(unit.lower()):
184
cnv = extra_conversions[unit.lower()]
186
# lookup in our custom nutrition-related conversion table
187
if self.conv.unit_dict.has_key(unit):
188
unit = self.conv.unit_dict[unit]
191
lookup = self.db.fetch_one(self.db.nutritionconversions_table,ingkey=key,unit=unit)
195
# otherwise, cycle through any units we have and see
196
# if we can get a conversion via those units...
197
for conv in self.db.fetch_all(self.db.nutritionconversions_table,ingkey=key):
198
factor = self.conv.converter(unit,conv.unit)
200
cnv = conv.factor*factor
202
return (0.01*amt)/cnv
204
def get_conversions (self, key=None, row=None):
205
"""Handed an ingredient key or a row of the nutrition database,
206
we return two dictionaries, one with Unit Conversions and the other
207
with densities. Our return dictionaries look like this:
208
({'chopped':1.03, #density dic
211
'leg':48,} # unit : grams
213
if not row: row=self.get_nutinfo(key)
214
if not row: return {},{}
217
for gd,gw in self.get_gramweights(row).items():
220
convfactor = self.conv.converter(u,'ml')
221
if convfactor: #if we are a volume
222
# divide mass by volume converted to mililiters
223
# (since gramwts are in grams!)
224
density = float(gw) / (a * convfactor)
227
# if we can't get a density from this amount, we're going to treat it as a unit!
228
if e: u = u + ", " + e
229
if a: gw = float(gw)/a
233
return densities,units
235
def get_densities (self,key=None,row=None):
236
"""Handed key or nutrow, return dictionary with densities."""
237
if not row: row = self._get_key(key)
238
if not row: return {}
239
if self.conv.density_table.has_key(key):
240
return {'':self.conv.density_table[key]}
243
for gd,gw in self.get_gramweights(row).items():
247
convfactor=self.conv.converter(u,'ml')
248
if convfactor: # if we are a volume
249
# divide mass by volume converted to milileters
250
# (gramwts are in grams)
251
density = float(gw) / (a * convfactor)
255
def get_gramweights (self,row):
256
"""Return a dictionary with gram weights.
259
nutweights = self.db.fetch_all(self.db.usda_weights_table,**{'ndbno':row.ndbno})
260
for nw in nutweights:
261
mtch = self.wght_breaker.match(nw.unit)
266
unit = mtch.groups()[0]
267
extra = mtch.groups()[2]
268
ret[(nw.amount,unit,extra)]=nw.gramwt
271
def get_density (self,key=None,row=None, fudge=True):
272
densities = self.get_densities(key,row)
273
if densities.has_key(''): densities[None]=densities['']
274
if key: keyrow=self._get_key(key)
276
if key and keyrow.density_equivalent and densities.has_key(keyrow.density_equivalent):
277
return densities[keyrow.density_equivalent]
278
elif densities.has_key(None):
279
self.conv.density_table[key]=densities[None]
280
return densities[None]
281
elif len(densities)==1:
282
return densities.values()[0]
284
return sum(densities.values())/len(densities)
288
def parse_gramweight_measure (self, txt):
289
m=self.gramwght_regexp.match(txt)
293
if amt: amt = float(amt)
296
return amt,unit,extra
298
def add_custom_nutrition_info (self, nutrition_dictionary):
299
"""Add custom nutritional information."""
300
#new_ndbno = self.db.increment_field(self.db.nutrition_table,'ndbno')
301
#if new_ndbno: nutrition_dictionary['ndbno']=new_ndbno
302
return self.db.do_add_nutrition(nutrition_dictionary).ndbno
306
"""A multipliable way to reference an object.
308
Any attribute of object that can be mutiplied, will be returned
311
We can also support various mathematical operators
312
n = NutritionInfo(obj, mult=2)
313
n * 2 -> NutritionInfo(obj,mult=4)
314
n2 = NutritionInfo(obj2, mult=3)
315
n2 + n -> NutritionInfoList([n2,n])
317
The result is that addition and multiplication 'makes sense' for
318
properties. For example, if we have nutrition info for 1 carrot,
319
we can multiply it or add it to the nutrition info for an
320
eggplant. The resulting object will reflect the appropriate
323
Carrot = NutritionInfo(CarrotNutritionRow)
324
Eggplant = NutritionInfo(EggplantNutritionRow)
328
(Carrot + Eggplant).kcal => 65
329
(Carrot * 3 + Eggplant).kcal => 147
331
This will be true for all numeric properties.
333
Non numeric properties return a somewhat not-useful string:
335
(Carrot + Eggplant).desc => 'CARROTS,RAW, EGGPLANT,RAW'
337
def __init__ (self,rowref, mult=1, fudged=False, ingObject=None):
338
self.__rowref__ = rowref
340
self.__fudged__ = fudged
341
self.__ingobject__ = ingObject
343
def __getattr__ (self, attr):
345
ret = getattr(self.__rowref__, attr)
347
if attr in SUMMABLE_FIELDS:
348
return (ret or 0) * self.__mult__
354
# somehow this magically gets us standard
355
# attribute handling...
356
raise AttributeError, attr
358
def __add__ (self, obj):
359
if isinstance(obj,NutritionInfo):
360
return NutritionInfoList([self,obj])
361
elif isinstance(obj,NutritionInfoList):
362
return NutritionInfoList([self]+obj.__nutinfos__)
364
def __mul__ (self, n):
365
return NutritionInfo(self.__rowref__, mult=self.__mult__ * n,
366
fudged=self.__fudged__,ingObject=self.__ingobject__)
368
KEY_VAPOR = 0 # when we don't have a key
369
UNIT_VAPOR = 1 # when we can't interpret the unit
370
DENSITY_VAPOR = 2 # when we don't have a density
371
AMOUNT_VAPOR = 3 # when there is no amount, leaving us quite confused
373
class NutritionVapor (NutritionInfo):
374
"""An object to hold our nutritional information before we know it.
376
Basically, we have to behave like a NutritionInfo class that doesn't
377
actually return any data.
379
We also can return information about why we're still vapor
380
(whether we need density info, key info or what...).
382
def __init__ (self, nd, key,
389
self.__rowref__ = rowref
392
self.__amt__ = amount
394
self.__ingobject__ = ingObject
397
"""Try to create matter from vapor and return it.
399
If we fail we return more vapor."""
400
if not self.__rowref__:
402
ni = self.__nd__.get_nutinfo(self.__key__)
403
if not isinstance(ni,NutritionVapor): return ni * self.__mult__
406
return self.__nd__.get_nutinfo_for_item(self.__key__,
409
ingObject=self.__ingobject__
412
c=self.__nd__.get_conversion_for_amt(self.__amt__,self.__unit__,self.__key__,fudge=False)
415
return NutritionInfo(self.__rowref__,
418
c=self.__nd__.get_conversion_for_amt(self.__amt__,self.__unit__,self.__key__,fudge=True)
421
return NutritionInfo(self.__rowref__,
423
ingObject=self.__ingobject__
427
else: return self.__nd__.get_nutinfo_for_item(self.__key__,self.__amt__,self.__unit__,ingObject=self.__ingobject__)
429
def __getattr__ (self,attr):
430
"""Return 0 for any requests for a non _ prefixed attribute."""
434
raise AttributeError,attr
437
return '<NutritionVapor %s>'%self.__key__
439
def __nonzero__ (self):
440
"""Vapor is always False."""
443
def _wheres_the_vapor (self):
444
"""Return a key as to why we're vapor."""
445
if not self.__rowref__: return KEY_VAPOR
446
elif not self.__amt__: return AMOUNT_VAPOR
447
else: return UNIT_VAPOR
449
class NutritionInfoList (list, NutritionInfo):
450
"""A summable list of objects.
452
When we ask for numeric attributes of our members, we get the sum.
454
def __init__ (self,nutinfos, mult=1,ingObject=None):
455
self.__nutinfos__ = nutinfos
456
#self.__len__ = self.__nutinfos__.__len__
457
#self.__getitem__ = self.__nutinfos__.__getitem__
459
self.__ingobject__ = ingObject
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>'
524
for i in self.__nutinfos__: yield i
526
def recursive_length (self):
527
"""Return number of contained nutrition info objects, recursing any embedded lists.
530
for x in range(len(self)):
532
if isinstance(obj,NutritionInfoList):
533
n += obj.recursive_length()
538
if __name__ == '__main__':
540
sys.path.append('/usr/share/')
541
import gourmet.recipeManager as rm
542
db=rm.RecipeManager(**rm.dbargs)
543
import gourmet.convert
544
conv = gourmet.convert.converter()
545
import nutritionGrabberGui
546
nutritionGrabberGui.check_for_db(db)
547
nd=NutritionData(db,conv)
550
from gourmet import convert
551
class SimpleInterface:
553
def __init__ (self, nd):
554
self.ACTIONS = {'Add ingredient':self.add_ingredient,
555
'Add key info':self.add_key,
556
'Print info':self.print_info,
563
choices = self.ACTIONS.keys()
564
for n,a in enumerate(choices):
568
choice = raw_input('Enter number of choice: ')
570
if choice < len(choices): choice = self.ACTIONS[choices[choice]]
580
def add_ingredient (self):
581
key=raw_input('Enter ingredient key: ')
582
amt = convert.frac_to_float(raw_input('Enter amount: '))
583
unit = raw_input('Enter unit: ')
585
self.ings = NutritionInfoList([self.nd.get_nutinfo_for_item(key,amt,unit)])
587
self.ings = self.ings + self.nd.get_nutinfo_for_item(key,amt,unit)
590
key=raw_input('Enter key for which we add info: ')
591
matches = self.nd.get_matches(key,10)
592
for n,m in enumerate(matches):
596
choice = raw_input('Enter number of choice: ')
598
if choice < len(matches): choice = matches[choice][1]
600
self.nd.set_key_from_ndbno(key,choice)
603
def print_info (self):
604
att = raw_input('What information would you like (e.g. kcal): ')
605
while not hasattr(self.ings,att):
606
print "I'm sorry, there is no information about ",att
607
att = raw_input('What information would you like (e.g. kcal): ')
608
print att,":",getattr(self.ings,att)
609
vv = self.ings._get_vapor()
611
print '(but we have some vapor)'
613
explanation = v._wheres_the_vapor()
614
print 'Vapor for ',v.__key__
615
if explanation==KEY_VAPOR: print 'No key'
616
if explanation==UNIT_VAPOR: print "Can't handle unit ",v.__unit__
617
if explanation==AMOUNT_VAPOR: print "What am I to do with the amount ",v.__amt__
623
si = SimpleInterface(nd)
627
#while raw_input('Get another density?'):
628
# row=random.choice(db.nutrition_table)
629
# print 'Information: ',row.desc, nd.get_conversions(row=row)
630
# #print 'Gramweights: ',nd.get_gramweights(row)
631
# #print 'Density of ',row.desc,' = ',nd.get_densities(row)