def test_get_recipe_with_yield(): recipe = Recipe( title="Test", yields=[Amount(factor=Decimal('2'), unit="servings")], ingredients=[ Ingredient(amount=Amount(factor=Decimal('5')), name='Eggs'), ], ) result = get_recipe_with_yield(recipe, Amount(factor=Decimal('4'), unit='servings')) assert result.yields[0] == Amount(factor=Decimal('4'), unit='servings') assert result.ingredients[0].amount == Amount(factor=Decimal('10')) # interpreted as "4 recipes", that is multiply by 4 result_unitless = get_recipe_with_yield(recipe, Amount(factor=Decimal('4'))) assert result_unitless.yields[0] == Amount(factor=Decimal('8'), unit='servings') assert result_unitless.ingredients[0].amount == Amount(factor=Decimal('20')) # if recipe has unitless yield, it is preferred to the above interpretation recipe_with_unitless_yield = replace(recipe, yields=[Amount(factor=Decimal('4'))]) result_unitless_from_unitless_yield = get_recipe_with_yield(recipe_with_unitless_yield, Amount(factor=Decimal('4'))) assert result_unitless_from_unitless_yield.yields[0] == Amount(factor=Decimal('4')) assert result_unitless_from_unitless_yield.ingredients[0].amount == Amount(factor=Decimal('5')) # try with unit not in recipe yields with pytest.raises(StopIteration): get_recipe_with_yield(recipe, Amount(factor=Decimal('500'), unit='ml'))
def _get_linked_recipe(ingredient: Ingredient, *, base_url: URL, parser: RecipeParser, flatten: bool = True) -> Recipe: url = base_url.join(URL(ingredient.link)) try: with urllib.request.urlopen(str(url)) as req: encoding = req.info().get_content_charset() or 'UTF-8' src = req.read().decode(encoding) except Exception as e: raise RuntimeError( f'''Couldn't find linked recipe for ingredient "{ingredient.name}"''' ) from e try: link_recipe = parser.parse(src) except Exception as e: raise RuntimeError( f'''Couldn't parse linked recipe for ingredient "{ingredient.name}"''' ) from e if flatten: link_recipe = _get_flattened_recipe(link_recipe, base_url=url, parser=parser) if ingredient.amount: try: link_recipe = get_recipe_with_yield(link_recipe, ingredient.amount) except StopIteration: print(_make_missing_yield_warning(link_recipe, ingredient.amount), file=sys.stderr) return link_recipe
def _process_scaling(r, args): if args.required_yield is not None: required_yield = RecipeParser.parse_amount(args.required_yield) if required_yield is None or required_yield.factor is None: print(f'Given yield is not valid', file=sys.stderr) exit(1) try: r = get_recipe_with_yield(r, required_yield) except StopIteration: print(f'Recipe "{r.title}" does not specify a yield in the unit "{required_yield.unit}". The ' f'following units can be used: ' + ", ".join(f'"{y.unit}"' for y in r.yields), file=sys.stderr) exit(1) elif args.multiply is not None: multiply = RecipeParser.parse_amount(args.multiply) if multiply is None or multiply.factor is None: print(f'Given multiplier is not valid', file=sys.stderr) exit(1) if multiply.unit is not None: print(f'A recipe can only be multiplied with a unitless amount', file=sys.stderr) exit(1) r = multiply_recipe(r, multiply.factor) return r
def _create_flattened_substituted_ingredients(ingredients: List[Union[Ingredient, IngredientGroup]], link_to_recipe: Dict[str, Recipe]) -> List[Ingredient]: result_ingredients = [] result_groups = [] for ingr in ingredients: if isinstance(ingr, IngredientGroup): new_group = copy.deepcopy(ingr) new_group.children = _create_flattened_substituted_ingredients(new_group.children, link_to_recipe) result_groups.append(new_group) elif ingr.link in link_to_recipe: try: link_recipe = get_recipe_with_yield(link_to_recipe[ingr.link], ingr.amount) except StopIteration: result_ingredients.append(ingr) else: new_group = IngredientGroup(title=_link_ingredient_title(ingr, link_recipe)) new_group.children = link_recipe.ingredients result_groups.append(new_group) else: result_ingredients.append(ingr) # groups must come after ingredients, see https://github.com/tstehr/RecipeMD/issues/6 return result_ingredients + result_groups
def _process_scaling(r, args): """Returns recipes scaled according to --multiply or --yield""" if args.required_yield is not None: required_yield = RecipeParser.parse_amount(args.required_yield) if required_yield is None or required_yield.factor is None: print(f'Given yield is not valid', file=sys.stderr) exit(1) try: r = get_recipe_with_yield(r, required_yield) except StopIteration: print(_make_missing_yield_warning(r, required_yield), file=sys.stderr) exit(1) elif args.multiply is not None: multiply = RecipeParser.parse_amount(args.multiply) if multiply is None or multiply.factor is None: print(f'Given multiplier is not valid', file=sys.stderr) exit(1) if multiply.unit is not None: print(f'A recipe can only be multiplied with a unitless amount', file=sys.stderr) exit(1) r = multiply_recipe(r, multiply.factor) return r
def download_file(relative_path=''): absolute_path = os.path.join(base_folder_path, relative_path) if os.path.isdir(absolute_path): if not absolute_path.endswith('/'): return redirect(f'/{relative_path}/', code=302) child_paths = [(ch, os.path.isdir(os.path.join(absolute_path, ch))) for ch in os.listdir(absolute_path)] child_paths = [ (ch, is_dir) for ch, is_dir in child_paths if not ch.startswith('.') and (is_dir or ch.endswith('.md')) ] child_paths = [ f'{ch}/' if not ch.endswith('/') and is_dir else ch for ch, is_dir in child_paths ] child_paths = sorted(child_paths) return render_template("folder.html", child_paths=child_paths, path=relative_path) if not absolute_path.endswith('.md'): return send_from_directory(base_folder_path, relative_path) with open(absolute_path, 'r', encoding='UTF-8') as f: required_yield_str = request.args.get('yield', '1') required_yield = recipe_parser.parse_amount(required_yield_str) if required_yield is None: required_yield = Amount(factor=Decimal(1)) src = f.read() try: recipe = recipe_parser.parse(src) except Exception as e: return render_template("markdown.html", markdown=src, path=relative_path, errors=[e.args[0]]) errors = [] try: recipe = get_recipe_with_yield(recipe, required_yield) except StopIteration: errors.append( f'The recipe does not specify a yield in the unit "{required_yield.unit}". ' f'The following units can be used: ' + ", ".join(f'"{y.unit}"' for y in recipe.yields)) except Exception as e: errors.append(str(e)) return render_template( "recipe.html", recipe=recipe, yields=recipe_serializer._serialize_yields(recipe.yields, rounding=2), tags=recipe_serializer._serialize_tags(recipe.tags), units=list(set(y.unit for y in recipe.yields)), default_yield=recipe_serializer._serialize_amount( recipe.yields[0]) if recipe.yields else "1", path=relative_path, errors=errors)