<commit_before># coding=utf-8
"""
Test plugin infrastructure and hooks.

Copyright 2018 Jared Crapo <jared@kotfu.net>
Released under MIT license, see LICENSE file
"""

from typing import Tuple

import pytest

import cmd2

from .conftest import StdOut

class Plugin:
    "A mixin class for testing hook registration and calling"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.reset_counters()

    def reset_counters(self):
        self.called_pph = 0

    def prepost_hook_one(self):
        self.poutput("one")

    def prepost_hook_two(self):
        self.poutput("two")

    def pph(self, statement: cmd2.Statement) -> Tuple[bool, cmd2.Statement]:
        self.called_pph += 1
        return False, statement

    def pph_stop(self, statement: cmd2.Statement) -> Tuple[bool, cmd2.Statement]:
        self.called_pph += 1
        return True, statement

    def pph_emptystatement(self, statement: cmd2.Statement) -> Tuple[bool, cmd2.Statement]:
        self.called_pph += 1
        raise cmd2.EmptyStatement

    def pph_exception(self, statement: cmd2.Statement) -> Tuple[bool, cmd2.Statement]:
        self.called_pph += 1
        raise ValueError

class PluggedApp(Plugin, cmd2.Cmd):
    "A sample app with a plugin mixed in"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def do_say(self, statement):
        """Repeat back the arguments"""
        self.poutput(statement)

###
#
# test hooks
#
###
def test_preloop_hook(capsys):
    app = PluggedApp()
    app.register_preloop_hook(app.prepost_hook_one)
    app.cmdqueue.append('say hello')
    app.cmdqueue.append('quit')
    app.cmdloop()
    out, err = capsys.readouterr()
    assert out == 'one\nhello\n'
    assert not err

def test_preloop_hooks(capsys):
    app = PluggedApp()
    app.register_preloop_hook(app.prepost_hook_one)
    app.register_preloop_hook(app.prepost_hook_two)
    app.cmdqueue.append('say hello')
    app.cmdqueue.append('quit')
    app.cmdloop()
    out, err = capsys.readouterr()
    assert out == 'one\ntwo\nhello\n'
    assert not err

def test_postloop_hook(capsys):
    app = PluggedApp()
    app.register_postloop_hook(app.prepost_hook_one)
    app.cmdqueue.append('say hello')
    app.cmdqueue.append('quit')
    app.cmdloop()
    out, err = capsys.readouterr()
    assert out == 'hello\none\n'
    assert not err

def test_postloop_hooks(capsys):
    app = PluggedApp()
    app.register_postloop_hook(app.prepost_hook_one)
    app.register_postloop_hook(app.prepost_hook_two)
    app.cmdqueue.append('say hello')
    app.cmdqueue.append('quit')
    app.cmdloop()
    out, err = capsys.readouterr()
    assert out == 'hello\none\ntwo\n'
    assert not err

def test_postparsing_hook(capsys):
    app = PluggedApp()
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert out == 'hello\n'
    assert not err
    assert not app.called_pph

    app.reset_counters()
    app.register_postparsing_hook(app.pph)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert out == 'hello\n'
    assert not err
    assert app.called_pph == 1

    # register the function again, so it should be called
    # twice
    app.reset_counters()
    app.register_postparsing_hook(app.pph)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert out == 'hello\n'
    assert not err
    assert app.called_pph == 2

def test_postparsing_hook_stop_first(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.pph_stop)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_pph == 1
    assert stop

    # register another function but it shouldn't be called
    app.reset_counters()
    app.register_postparsing_hook(app.pph)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_pph == 1
    assert stop

def test_postparsing_hook_stop_second(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.pph)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_pph == 1
    assert not stop

    # register another function and make sure it gets called
    app.reset_counters()
    app.register_postparsing_hook(app.pph_stop)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_pph == 2
    assert stop

    # register a third function which shouldn't be called
    app.reset_counters()
    app.register_postparsing_hook(app.pph)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_pph == 2
    assert stop

def test_postparsing_hook_emptystatement_first(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.pph_emptystatement)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    assert app.called_pph == 1

    # register another function but it shouldn't be called
    app.reset_counters()
    stop = app.register_postparsing_hook(app.pph)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    assert app.called_pph == 1

def test_postparsing_hook_emptystatement_second(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.pph)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert out == 'hello\n'
    assert not err
    assert app.called_pph == 1

    # register another function and make sure it gets called
    app.reset_counters()
    app.register_postparsing_hook(app.pph_emptystatement)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    assert app.called_pph == 2

    # register a third function which shouldn't be called
    app.reset_counters()
    app.register_postparsing_hook(app.pph)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    assert app.called_pph == 2

def test_postparsing_hook_exception(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.pph_exception)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert err
    assert app.called_pph == 1

    # register another function, but it shouldn't be called
    app.reset_counters()
    stop = app.register_postparsing_hook(app.pph)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert err
    assert app.called_pph == 1
<commit_msg>Tests for precmd hooks<commit_after># coding=utf-8
"""
Test plugin infrastructure and hooks.

Copyright 2018 Jared Crapo <jared@kotfu.net>
Released under MIT license, see LICENSE file
"""

from typing import Tuple

import pytest

import cmd2

from .conftest import StdOut

class Plugin:
    "A mixin class for testing hook registration and calling"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.reset_counters()

    def reset_counters(self):
        self.called_postparsing = 0
        self.called_precmd = 0

    def prepost_hook_one(self):
        "Method used for preloop or postloop hooks"
        self.poutput("one")

    def prepost_hook_two(self):
        "Another method used for preloop or postloop hooks"
        self.poutput("two")

    def postparse_hook(self, statement: cmd2.Statement) -> Tuple[bool, cmd2.Statement]:
        "A postparsing hook"
        self.called_postparsing += 1
        return False, statement

    def postparse_hook_stop(self, statement: cmd2.Statement) -> Tuple[bool, cmd2.Statement]:
        "A postparsing hook with requests application exit"
        self.called_postparsing += 1
        return True, statement

    def postparse_hook_emptystatement(self, statement: cmd2.Statement) -> Tuple[bool, cmd2.Statement]:
        "A postparsing hook with raises an EmptyStatement exception"
        self.called_postparsing += 1
        raise cmd2.EmptyStatement

    def postparse_hook_exception(self, statement: cmd2.Statement) -> Tuple[bool, cmd2.Statement]:
        "A postparsing hook which raises an exception"
        self.called_postparsing += 1
        raise ValueError

    def precmd(self, statement: cmd2.Statement) -> cmd2.Statement:
        "Override cmd.Cmd method"
        self.called_precmd += 1
        return statement

    def precmd_hook(self, statement: cmd2.Statement) -> cmd2.Statement:
        "A precommand hook"
        self.called_precmd += 1
        return statement

    def precmd_hook_emptystatement(self, statement: cmd2.Statement) -> cmd2.Statement:
        "A precommand hook which raises an EmptyStatement exception"
        self.called_precmd += 1
        raise cmd2.EmptyStatement

    def precmd_hook_exception(self, statement: cmd2.Statement) -> cmd2.Statement:
        "A precommand hook which raises an exception"
        self.called_precmd += 1
        raise ValueError

class PluggedApp(Plugin, cmd2.Cmd):
    "A sample app with a plugin mixed in"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def do_say(self, statement):
        """Repeat back the arguments"""
        self.poutput(statement)

###
#
# test pre and postloop hooks
#
###
def test_preloop_hook(capsys):
    app = PluggedApp()
    app.register_preloop_hook(app.prepost_hook_one)
    app.cmdqueue.append('say hello')
    app.cmdqueue.append('quit')
    app.cmdloop()
    out, err = capsys.readouterr()
    assert out == 'one\nhello\n'
    assert not err

def test_preloop_hooks(capsys):
    app = PluggedApp()
    app.register_preloop_hook(app.prepost_hook_one)
    app.register_preloop_hook(app.prepost_hook_two)
    app.cmdqueue.append('say hello')
    app.cmdqueue.append('quit')
    app.cmdloop()
    out, err = capsys.readouterr()
    assert out == 'one\ntwo\nhello\n'
    assert not err

def test_postloop_hook(capsys):
    app = PluggedApp()
    app.register_postloop_hook(app.prepost_hook_one)
    app.cmdqueue.append('say hello')
    app.cmdqueue.append('quit')
    app.cmdloop()
    out, err = capsys.readouterr()
    assert out == 'hello\none\n'
    assert not err

def test_postloop_hooks(capsys):
    app = PluggedApp()
    app.register_postloop_hook(app.prepost_hook_one)
    app.register_postloop_hook(app.prepost_hook_two)
    app.cmdqueue.append('say hello')
    app.cmdqueue.append('quit')
    app.cmdloop()
    out, err = capsys.readouterr()
    assert out == 'hello\none\ntwo\n'
    assert not err

###
#
# test postparsing hooks
#
###
def test_postparsing_hook(capsys):
    app = PluggedApp()
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert out == 'hello\n'
    assert not err
    assert not app.called_postparsing

    app.reset_counters()
    app.register_postparsing_hook(app.postparse_hook)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert out == 'hello\n'
    assert not err
    assert app.called_postparsing == 1

    # register the function again, so it should be called twice
    app.reset_counters()
    app.register_postparsing_hook(app.postparse_hook)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert out == 'hello\n'
    assert not err
    assert app.called_postparsing == 2

def test_postparsing_hook_stop_first(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.postparse_hook_stop)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_postparsing == 1
    assert stop

    # register another function but it shouldn't be called
    app.reset_counters()
    app.register_postparsing_hook(app.postparse_hook)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_postparsing == 1
    assert stop

def test_postparsing_hook_stop_second(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.postparse_hook)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_postparsing == 1
    assert not stop

    # register another function and make sure it gets called
    app.reset_counters()
    app.register_postparsing_hook(app.postparse_hook_stop)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_postparsing == 2
    assert stop

    # register a third function which shouldn't be called
    app.reset_counters()
    app.register_postparsing_hook(app.postparse_hook)
    stop = app.onecmd_plus_hooks('say hello')
    assert app.called_postparsing == 2
    assert stop

def test_postparsing_hook_emptystatement_first(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.postparse_hook_emptystatement)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    assert app.called_postparsing == 1

    # register another function but it shouldn't be called
    app.reset_counters()
    stop = app.register_postparsing_hook(app.postparse_hook)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    assert app.called_postparsing == 1

def test_postparsing_hook_emptystatement_second(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.postparse_hook)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert out == 'hello\n'
    assert not err
    assert app.called_postparsing == 1

    # register another function and make sure it gets called
    app.reset_counters()
    app.register_postparsing_hook(app.postparse_hook_emptystatement)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    assert app.called_postparsing == 2

    # register a third function which shouldn't be called
    app.reset_counters()
    app.register_postparsing_hook(app.postparse_hook)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    assert app.called_postparsing == 2

def test_postparsing_hook_exception(capsys):
    app = PluggedApp()
    app.register_postparsing_hook(app.postparse_hook_exception)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert err
    assert app.called_postparsing == 1

    # register another function, but it shouldn't be called
    app.reset_counters()
    stop = app.register_postparsing_hook(app.postparse_hook)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert err
    assert app.called_postparsing == 1

###
#
# test precmd hooks
#
#####
def test_precmd_hook(capsys):
    app = PluggedApp()
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert out == 'hello\n'
    assert not err
    # without registering any hooks, precmd() should be called
    assert app.called_precmd == 1

    app.reset_counters()
    app.register_precmd_hook(app.precmd_hook)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert out == 'hello\n'
    assert not err
    # with one hook registered, we should get precmd() and the hook
    assert app.called_precmd == 2

    # register the function again, so it should be called twice
    app.reset_counters()
    app.register_precmd_hook(app.precmd_hook)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert out == 'hello\n'
    assert not err
    # with two hooks registered, we should get precmd() and both hooks
    assert app.called_precmd == 3

def test_precmd_hook_emptystatement_first(capsys):
    app = PluggedApp()
    app.register_precmd_hook(app.precmd_hook_emptystatement)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    # since the registered hooks are called before precmd(), if a registered
    # hook throws an exception, precmd() is never called
    assert app.called_precmd == 1

    # register another function but it shouldn't be called
    app.reset_counters()
    stop = app.register_precmd_hook(app.precmd_hook)
    app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    # the exception raised by the first hook should prevent the second
    # hook from being called, and it also prevents precmd() from being
    # called
    assert app.called_precmd == 1

def test_precmd_hook_emptystatement_second(capsys):
    app = PluggedApp()
    app.register_precmd_hook(app.precmd_hook)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert out == 'hello\n'
    assert not err
    # with one hook registered, we should get precmd() and the hook
    assert app.called_precmd == 2

    # register another function and make sure it gets called
    app.reset_counters()
    app.register_precmd_hook(app.precmd_hook_emptystatement)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    # since the registered hooks are called before precmd(), if a registered
    # hook throws an exception, precmd() is never called
    assert app.called_precmd == 2

    # register a third function which shouldn't be called
    app.reset_counters()
    app.register_precmd_hook(app.precmd_hook)
    stop = app.onecmd_plus_hooks('say hello')
    out, err = capsys.readouterr()
    assert not stop
    assert not out
    assert not err
    # the exception raised by the second hook should prevent the third
    # hook from being called. since the registered hooks are called before precmd(),
    # if a registered hook throws an exception, precmd() is never called
    assert app.called_precmd == 2

###
#
# test postcmd hooks
#
####
def test_postcmd(capsys):
    pass

##
#
# command finalization
#
###
def test_cmdfinalization(capsys):
    pass

<|endoftext|>


<commit_before>#!/usr/bin/env python

from flask import Flask, render_template, flash, request, send_file
from flask_uploads import UploadSet, DATA, configure_uploads
from wtforms import validators, widgets, Form, Field, FormField, FieldList, TextField, TextAreaField, BooleanField, DecimalField, IntegerField, SelectField, SelectMultipleField, FileField
from werkzeug.utils import secure_filename

import os

import recipe as drink_recipe
import util
import formatted_menu
from barstock import Barstock

# app config
app = Flask(__name__)
app.config.from_object(__name__)
with open('local_secret') as fp:
    app.config['SECRET_KEY'] = fp.read().strip()
app.config['UPLOADS_DEFAULT_DEST'] = './stockdb'
datafiles = UploadSet('datafiles', DATA)
configure_uploads(app, (datafiles,))

class MixMindServer():
    def __init__(self, recipes=['recipes_schubar.json'], barstock_files=['BarSchueyStock.csv', 'christmas_schubar.csv']):
        self.recipe_files = recipes
        self.barstock_files = barstock_files
        base_recipes = util.load_recipe_json(recipes)
        self.barstock = Barstock.load(barstock_files, True)
        self.recipes = [drink_recipe.DrinkRecipe(name, recipe).generate_examples(self.barstock) for name, recipe in base_recipes.iteritems()]
    def get_ingredients_table(self):
        df = self.barstock.sorted_df()
        df.Proof = df.Proof.astype('int')
        df['Size (mL)'] = df['Size (mL)'].astype('int')
        table = df.to_html(index=False,
                columns='Category,Type,Bottle,Proof,Size (mL),Price Paid'.split(','))
        return table
mms = MixMindServer()

class CSVField(Field):
    widget = widgets.TextInput()

    def _value(self):
        if self.data:
            return u', '.join(self.data)
        else:
            return u''

    def process_formdata(self, valuelist):
        if valuelist[0]:
            self.data = [x.strip().lower() for x in valuelist[0].split(',') if x.strip()]
        else:
            self.data = []

def pairs(l):
    return [(x,x) for x in l]

class DrinksForm(Form):
    def reset(self):
        blankData = MultiDict([('csrf', self.reset_csrf())])
        self.process(blankData)

    # display options
    prices = BooleanField("Prices", description="Display prices for drinks based on stock")
    prep_line = BooleanField("Preparation", description="Display a line showing glass, ice, and prep")
    stats = BooleanField("Stats", description="Print out a detailed statistics block for the selected recipes")
    examples = BooleanField("Examples", description="Show specific examples of a recipe based on the ingredient stock")
    all_ingredients = BooleanField("All Ingredients", description="Show every ingredient instead of just the main liquors with each example")
    convert = TextField("Convert", description="Convert recipes to a different primary unit", default=None, validators=[validators.AnyOf(util.VALID_UNITS), validators.Optional()])
    markup = DecimalField("Markup", description="Drink markup: price = ceil((base_cost+1)*markup)", default=1.2)
    info = BooleanField("Info", description="Show the info line for recipes")
    origin = BooleanField("Origin", description="Check origin and mark drinks as Schubar originals")
    variants = BooleanField("Variants", description="Show variants for drinks")

    # filtering options
    all = BooleanField("Allow all ingredients", description="Include all recipes, regardless of if they can be made from the loaded barstock")
    include = CSVField("Include Ingredients", description="Filter by ingredient(s) that must be contained in the recipe")
    exclude = CSVField("Exclude Ingredients", description="Filter by ingredient(s) that must NOT be contained in the recipe")
    use_or = BooleanField("Logical OR", description="Use logical OR for included and excluded ingredient lists instead of default AND")
    name = TextField("Name", description="Filter by a cocktail's name")
    style = SelectField("Style", description="Include drinks matching the style such as After Dinner or Longdrink", choices=pairs(['','All Day Cocktail','Before Dinner Cocktail','After Dinner Cocktail','Longdrink', 'Hot Drink', 'Sparkling Cocktail', 'Wine Cocktail']))
    glass = SelectField("Glass", description="Include drinks matching the glass type such as cocktail or rocks", choices=pairs(['','cocktail','rocks','highball','flute','shot']))
    prep = SelectField("Prep", description="Include drinks matching the prep method such as shake or build", choices=pairs(['','shake', 'stir', 'build']))
    ice = SelectField("Ice", description="Include drinks matching the ice used such as crushed", choices=pairs(['','cubed','chushed','neat']))

    # sorting options
    # name
    # main ingredient
    # price
    # IBA classification

    # pdf options
    pdf_filename = TextField("Filename to use", description="Basename of the pdf and tex files generated", default="web_drinks_file")
    ncols = IntegerField("Number of columns", default=2, description="Number of columns to use for the menu")
    liquor_list = BooleanField("Liquor list", description="Show list of the available ingredients")
    liquor_list_own_page = BooleanField("Liquor list (own page)", description="Show list of the available ingredients on a separate page")
    debug = BooleanField("LaTeX debug output", description="Add debugging output to the pdf")
    align = BooleanField("Align items", description="Align drink names across columns")
    title = TextField("Title", description="Title to use")
    tagline = TextField("Tagline", description="Tagline to use below the title")

class RecipeIngredientForm(Form):
    ingredient = TextField("Ingredient", validators=[validators.required()])
    quantity = DecimalField("Quantity", validators=[validators.required()])
    is_optional = BooleanField("Optional")
class RecipeForm(Form):
    def reset(self):
        blankData = MultiDict([('csrf', self.reset_csrf())])
        self.process(blankData)
    name = TextField("Name", description="The recipe name", validators=[validators.required()])
    info = TextField("Info", description="Additional information about the recipe")
    ingredients = FieldList(FormField(RecipeIngredientForm), min_entries=1, validators=[validators.required()])
    unit = SelectField("Unit", choices=pairs([util.VALID_UNITS]), validators=[validators.required()])
    #glass =
    #unit =
    #prep =
    #ice =
    #garnish =

class RecipeListSelector(Form):
    def reset(self):
        blankData = MultiDict([('csrf', self.reset_csrf())])
        self.process(blankData)
    recipes = SelectMultipleField("Available Recipe Lists", description="Select recipe lists to be used for generating a menu",
            choices=[("recipes_schubar.json", "Core Recipes (from @Schubar)"),
                ("IBA_unforgettables.json", "IBA Unforgettables"),
                ("IBA_contemporary_classics.json", "IBA Contemporary Classics"),
                ("IBA_new_era_drinks.json", "IBA New Era Drinks")])

class BarstockForm(Form):
    def reset(self):
        blankData = MultiDict([('csrf', self.reset_csrf())])
        self.process(blankData)
    upload_csv = FileField("Upload a Barstock CSV", [validators.regexp(ur'^[^/\\]\.csv$')])

    categories = 'Spirit,Liqueur,Vermouth,Bitters,Syrup,Dry,Juice,Mixer,Wine,Ice'.split(',')
    types = ',Brandy,Dry Gin,Genever,Amber Rum,White Rum,Dark Rum,Rye Whiskey,Vodka,Orange Liqueur,Dry Vermouth,Sweet Vermouth,Aromatic Bitters,Orange Bitters,Fruit Bitters,Bourbon Whiskey,Tennessee Whiskey,Irish Whiskey,Scotch Whisky,Silver Tequila,Gold Tequila,Mezcal,Aquavit,Amaretto,Blackberry Liqueur,Raspberry Liqueur,Campari,Amaro,Cynar,Aprol,Creme de Cacao,Creme de Menthe,Grenadine,Simple Syrup,Rich Simple Syrup,Honey Syrup,Orgeat,Maple Syrup,Sugar'.split(',')
    category = SelectField("Category", validators=[validators.required()], choices=pairs(categories))
    type_ = SelectField("Type", validators=[validators.required()], choices=pairs(types))
    bottle = TextField("Bottle", description='Specify the bottle, e.g. "Bulliet Rye", "Beefeater", "Tito\'s", or "Bacardi Carta Blanca"', validators=[validators.required()])
    proof = DecimalField("Proof", description="Proof rating of the ingredient, if any", validators=[validators.required(), validators.NumberRange(min=0, max=200)])
    size_ml = DecimalField("Size (mL)", description="Size of the ingredient in mL", validators=[validators.required(), validators.NumberRange(min=0, max=20000)])
    price = DecimalField("Price ($)", description="Price paid or approximate market value in USD", validators=[validators.required(), validators.NumberRange(min=0, max=2000)])

def bundle_options(tuple_class, args):
    return tuple_class(*(getattr(args, field).data for field in tuple_class._fields))

def recipes_from_options(form, to_html=False):
    display_options = bundle_options(util.DisplayOptions, form)
    filter_options = bundle_options(util.FilterOptions, form)
    recipes, excluded = util.filter_recipes(mms.recipes, filter_options)
    if form.convert.data:
        map(lambda r: r.convert(form.convert.data), recipes)
    if form.stats and recipes:
        stats = util.report_stats(recipes, as_html=True)
    if to_html:
        recipes = [formatted_menu.format_recipe_html(recipe, display_options) for recipe in recipes]
    return recipes, excluded, stats

@app.route("/", methods=['GET', 'POST'])
def mainpage():
    form = DrinksForm(request.form)
    print form.errors
    recipes = []
    excluded = None

    if request.method == 'POST':
        if form.validate():
            print request
            recipes, excluded, stats = recipes_from_options(form, to_html=True)
            flash("Settings applied. Showing {} available recipes".format(len(recipes)))
        else:
            flash("Error in form validation")

    return render_template('application_main.html', form=form, recipes=recipes, excluded=excluded, stats=stats)

@app.route("/download/", methods=['POST'])
def menu_download():
    form = DrinksForm(request.form)
    print form.errors

    if form.validate():
        print request
        recipes, _, _ = recipes_from_options(form)

        display_options = bundle_options(util.DisplayOptions, form)
        form.pdf_filename.data = 'menus/{}'.format(formatted_menu.filename_from_options(bundle_options(util.PdfOptions, form), display_options))
        pdf_options = bundle_options(util.PdfOptions, form)
        pdf_file = '{}.pdf'.format(pdf_options.pdf_filename)

        formatted_menu.generate_recipes_pdf(recipes, pdf_options, display_options, mms.barstock.df)
        return send_file(os.path.abspath(pdf_file), 'application/pdf', as_attachment=True, attachment_filename=pdf_file.lstrip('menus/'))

    else:
        flash("Error in form validation")
        return render_template('application_main.html', form=form, recipes=[], excluded=None)

@app.route("/select/", methods=['GET', 'POST'])
def mainpage_filter_only():
    form = DrinksForm(request.form)
    print form.errors
    recipes = []
    excluded = None

    if request.method == 'POST':
        if form.validate():
            print request
            display_options = util.DisplayOptions(False,False,False,False,0,False,False,True,True)
            filter_options = bundle_options(util.FilterOptions, form)
            recipes, excluded = util.filter_recipes(mms.recipes, filter_options)
            recipes = [formatted_menu.format_recipe_html(recipe, display_options) for recipe in recipes]
            flash("Settings applied. Showing {} available recipes".format(len(recipes)))
        else:
            flash("Error in form validation")

    return render_template('filter_only.html', form=form, recipes=recipes, excluded=excluded)

@app.route("/recipes/", methods=['GET','POST'])
def recipes():
    global mms
    select_form = RecipeListSelector(request.form)
    print select_form.errors
    add_form = RecipeForm(request.form)
    print add_form.errors

    if request.method == 'POST':
        print request
        if 'recipe-list-select' in request.form:
            recipes = select_form.recipes.data
            mms = MixMindServer(recipes=recipes, barstock_files=mms.barstock_files)
            flash("Now using recipes from {}".format(recipes))

    return render_template('recipes.html', select_form=select_form, add_form=add_form)

@app.route("/ingredients/", methods=['GET','POST'])
def ingredients():
    global mms
    form = BarstockForm(request.form)
    print form.errors

    if request.method == 'POST':
        print request
        if 'add-ingredient' in request.form:
            row = {}
            row['Category'] = form.category.data
            row['Type'] = form.type_.data
            row['Bottle'] = form.bottle.data
            row['Proof'] = float(form.proof.data)
            row['Size (mL)'] = float(form.size_ml.data)
            row['Price Paid'] = float(form.price.data)
            mms.barstock.add_row(row)

        elif 'remove-ingredient' in request.form:
            bottle = form.bottle.data
            if bottle in mms.barstock.df.Bottle.values:
                mms.barstock.df = mms.barstock.df[mms.barstock.df.Bottle != bottle]
                flash("Removed {}".format(bottle))
            else:
                flash("Error: \"{}\" not found; must match as shown below exactly".format(bottle))

        elif 'upload-csv' in request.form:
            filename = datafiles.save(request.files['upload_csv'])
            print "CSV uploaded to {}".format(filename)
            mms = MixMindServer(recipes=mms.recipe_files, barstock_files=[filename])
            flash("Ingredients database reloaded from {}".format(filename))

    table = mms.get_ingredients_table()
    return render_template('ingredients.html', form=form, table=table)

@app.route('/drinks.html')
def drinks_page():
    return app.send_static_file('drinks.html')

@app.route('/json/<recipe_name>')
def recipe_json(recipe_name):
    try:
        return str(mms.recipes[recipe_name])
    except KeyError:
        return "{} not found".format(recipe_name)

<commit_msg>Fix unbound local and remove 'Ice' from the ingredients table<commit_after>#!/usr/bin/env python

from flask import Flask, render_template, flash, request, send_file
from flask_uploads import UploadSet, DATA, configure_uploads
from wtforms import validators, widgets, Form, Field, FormField, FieldList, TextField, TextAreaField, BooleanField, DecimalField, IntegerField, SelectField, SelectMultipleField, FileField
from werkzeug.utils import secure_filename

import os

import recipe as drink_recipe
import util
import formatted_menu
from barstock import Barstock

# app config
app = Flask(__name__)
app.config.from_object(__name__)
with open('local_secret') as fp:
    app.config['SECRET_KEY'] = fp.read().strip()
app.config['UPLOADS_DEFAULT_DEST'] = './stockdb'
datafiles = UploadSet('datafiles', DATA)
configure_uploads(app, (datafiles,))

class MixMindServer():
    def __init__(self, recipes=['recipes_schubar.json'], barstock_files=['BarSchueyStock.csv', 'christmas_schubar.csv']):
        self.recipe_files = recipes
        self.barstock_files = barstock_files
        base_recipes = util.load_recipe_json(recipes)
        self.barstock = Barstock.load(barstock_files, True)
        self.recipes = [drink_recipe.DrinkRecipe(name, recipe).generate_examples(self.barstock) for name, recipe in base_recipes.iteritems()]
    def get_ingredients_table(self):
        df = self.barstock.sorted_df()
        df.Proof = df.Proof.astype('int')
        df['Size (mL)'] = df['Size (mL)'].astype('int')
        df = df[df.Category != 'Ice']
        table = df.to_html(index=False,
                columns='Category,Type,Bottle,Proof,Size (mL),Price Paid'.split(','))
        return table
mms = MixMindServer()

class CSVField(Field):
    widget = widgets.TextInput()

    def _value(self):
        if self.data:
            return u', '.join(self.data)
        else:
            return u''

    def process_formdata(self, valuelist):
        if valuelist[0]:
            self.data = [x.strip().lower() for x in valuelist[0].split(',') if x.strip()]
        else:
            self.data = []

def pairs(l):
    return [(x,x) for x in l]

class DrinksForm(Form):
    def reset(self):
        blankData = MultiDict([('csrf', self.reset_csrf())])
        self.process(blankData)

    # display options
    prices = BooleanField("Prices", description="Display prices for drinks based on stock")
    prep_line = BooleanField("Preparation", description="Display a line showing glass, ice, and prep")
    stats = BooleanField("Stats", description="Print out a detailed statistics block for the selected recipes")
    examples = BooleanField("Examples", description="Show specific examples of a recipe based on the ingredient stock")
    all_ingredients = BooleanField("All Ingredients", description="Show every ingredient instead of just the main liquors with each example")
    convert = TextField("Convert", description="Convert recipes to a different primary unit", default=None, validators=[validators.AnyOf(util.VALID_UNITS), validators.Optional()])
    markup = DecimalField("Markup", description="Drink markup: price = ceil((base_cost+1)*markup)", default=1.2)
    info = BooleanField("Info", description="Show the info line for recipes")
    origin = BooleanField("Origin", description="Check origin and mark drinks as Schubar originals")
    variants = BooleanField("Variants", description="Show variants for drinks")

    # filtering options
    all = BooleanField("Allow all ingredients", description="Include all recipes, regardless of if they can be made from the loaded barstock")
    include = CSVField("Include Ingredients", description="Filter by ingredient(s) that must be contained in the recipe")
    exclude = CSVField("Exclude Ingredients", description="Filter by ingredient(s) that must NOT be contained in the recipe")
    use_or = BooleanField("Logical OR", description="Use logical OR for included and excluded ingredient lists instead of default AND")
    name = TextField("Name", description="Filter by a cocktail's name")
    style = SelectField("Style", description="Include drinks matching the style such as After Dinner or Longdrink", choices=pairs(['','All Day Cocktail','Before Dinner Cocktail','After Dinner Cocktail','Longdrink', 'Hot Drink', 'Sparkling Cocktail', 'Wine Cocktail']))
    glass = SelectField("Glass", description="Include drinks matching the glass type such as cocktail or rocks", choices=pairs(['','cocktail','rocks','highball','flute','shot']))
    prep = SelectField("Prep", description="Include drinks matching the prep method such as shake or build", choices=pairs(['','shake', 'stir', 'build']))
    ice = SelectField("Ice", description="Include drinks matching the ice used such as crushed", choices=pairs(['','cubed','chushed','neat']))

    # sorting options
    # name
    # main ingredient
    # price
    # IBA classification

    # pdf options
    pdf_filename = TextField("Filename to use", description="Basename of the pdf and tex files generated", default="web_drinks_file")
    ncols = IntegerField("Number of columns", default=2, description="Number of columns to use for the menu")
    liquor_list = BooleanField("Liquor list", description="Show list of the available ingredients")
    liquor_list_own_page = BooleanField("Liquor list (own page)", description="Show list of the available ingredients on a separate page")
    debug = BooleanField("LaTeX debug output", description="Add debugging output to the pdf")
    align = BooleanField("Align items", description="Align drink names across columns")
    title = TextField("Title", description="Title to use")
    tagline = TextField("Tagline", description="Tagline to use below the title")

class RecipeIngredientForm(Form):
    ingredient = TextField("Ingredient", validators=[validators.required()])
    quantity = DecimalField("Quantity", validators=[validators.required()])
    is_optional = BooleanField("Optional")
class RecipeForm(Form):
    def reset(self):
        blankData = MultiDict([('csrf', self.reset_csrf())])
        self.process(blankData)
    name = TextField("Name", description="The recipe name", validators=[validators.required()])
    info = TextField("Info", description="Additional information about the recipe")
    ingredients = FieldList(FormField(RecipeIngredientForm), min_entries=1, validators=[validators.required()])
    unit = SelectField("Unit", choices=pairs([util.VALID_UNITS]), validators=[validators.required()])
    #glass =
    #unit =
    #prep =
    #ice =
    #garnish =

class RecipeListSelector(Form):
    def reset(self):
        blankData = MultiDict([('csrf', self.reset_csrf())])
        self.process(blankData)
    recipes = SelectMultipleField("Available Recipe Lists", description="Select recipe lists to be used for generating a menu",
            choices=[("recipes_schubar.json", "Core Recipes (from @Schubar)"),
                ("IBA_unforgettables.json", "IBA Unforgettables"),
                ("IBA_contemporary_classics.json", "IBA Contemporary Classics"),
                ("IBA_new_era_drinks.json", "IBA New Era Drinks")])

class BarstockForm(Form):
    def reset(self):
        blankData = MultiDict([('csrf', self.reset_csrf())])
        self.process(blankData)
    upload_csv = FileField("Upload a Barstock CSV", [validators.regexp(ur'^[^/\\]\.csv$')])

    categories = 'Spirit,Liqueur,Vermouth,Bitters,Syrup,Dry,Juice,Mixer,Wine,Ice'.split(',')
    types = ',Brandy,Dry Gin,Genever,Amber Rum,White Rum,Dark Rum,Rye Whiskey,Vodka,Orange Liqueur,Dry Vermouth,Sweet Vermouth,Aromatic Bitters,Orange Bitters,Fruit Bitters,Bourbon Whiskey,Tennessee Whiskey,Irish Whiskey,Scotch Whisky,Silver Tequila,Gold Tequila,Mezcal,Aquavit,Amaretto,Blackberry Liqueur,Raspberry Liqueur,Campari,Amaro,Cynar,Aprol,Creme de Cacao,Creme de Menthe,Grenadine,Simple Syrup,Rich Simple Syrup,Honey Syrup,Orgeat,Maple Syrup,Sugar'.split(',')
    category = SelectField("Category", validators=[validators.required()], choices=pairs(categories))
    type_ = SelectField("Type", validators=[validators.required()], choices=pairs(types))
    bottle = TextField("Bottle", description='Specify the bottle, e.g. "Bulliet Rye", "Beefeater", "Tito\'s", or "Bacardi Carta Blanca"', validators=[validators.required()])
    proof = DecimalField("Proof", description="Proof rating of the ingredient, if any", validators=[validators.required(), validators.NumberRange(min=0, max=200)])
    size_ml = DecimalField("Size (mL)", description="Size of the ingredient in mL", validators=[validators.required(), validators.NumberRange(min=0, max=20000)])
    price = DecimalField("Price ($)", description="Price paid or approximate market value in USD", validators=[validators.required(), validators.NumberRange(min=0, max=2000)])

def bundle_options(tuple_class, args):
    return tuple_class(*(getattr(args, field).data for field in tuple_class._fields))

def recipes_from_options(form, to_html=False):
    display_options = bundle_options(util.DisplayOptions, form)
    filter_options = bundle_options(util.FilterOptions, form)
    recipes, excluded = util.filter_recipes(mms.recipes, filter_options)
    if form.convert.data:
        map(lambda r: r.convert(form.convert.data), recipes)
    if form.stats and recipes:
        stats = util.report_stats(recipes, as_html=True)
    if to_html:
        recipes = [formatted_menu.format_recipe_html(recipe, display_options) for recipe in recipes]
    return recipes, excluded, stats

@app.route("/", methods=['GET', 'POST'])
def mainpage():
    form = DrinksForm(request.form)
    print form.errors
    recipes = []
    excluded = None
    stats = None

    if request.method == 'POST':
        if form.validate():
            print request
            recipes, excluded, stats = recipes_from_options(form, to_html=True)
            flash("Settings applied. Showing {} available recipes".format(len(recipes)))
        else:
            flash("Error in form validation")

    return render_template('application_main.html', form=form, recipes=recipes, excluded=excluded, stats=stats)

@app.route("/download/", methods=['POST'])
def menu_download():
    form = DrinksForm(request.form)
    print form.errors

    if form.validate():
        print request
        recipes, _, _ = recipes_from_options(form)

        display_options = bundle_options(util.DisplayOptions, form)
        form.pdf_filename.data = 'menus/{}'.format(formatted_menu.filename_from_options(bundle_options(util.PdfOptions, form), display_options))
        pdf_options = bundle_options(util.PdfOptions, form)
        pdf_file = '{}.pdf'.format(pdf_options.pdf_filename)

        formatted_menu.generate_recipes_pdf(recipes, pdf_options, display_options, mms.barstock.df)
        return send_file(os.path.abspath(pdf_file), 'application/pdf', as_attachment=True, attachment_filename=pdf_file.lstrip('menus/'))

    else:
        flash("Error in form validation")
        return render_template('application_main.html', form=form, recipes=[], excluded=None)

@app.route("/select/", methods=['GET', 'POST'])
def mainpage_filter_only():
    form = DrinksForm(request.form)
    print form.errors
    recipes = []
    excluded = None

    if request.method == 'POST':
        if form.validate():
            print request
            display_options = util.DisplayOptions(False,False,False,False,0,False,False,True,True)
            filter_options = bundle_options(util.FilterOptions, form)
            recipes, excluded = util.filter_recipes(mms.recipes, filter_options)
            recipes = [formatted_menu.format_recipe_html(recipe, display_options) for recipe in recipes]
            flash("Settings applied. Showing {} available recipes".format(len(recipes)))
        else:
            flash("Error in form validation")

    return render_template('filter_only.html', form=form, recipes=recipes, excluded=excluded)

@app.route("/recipes/", methods=['GET','POST'])
def recipes():
    global mms
    select_form = RecipeListSelector(request.form)
    print select_form.errors
    add_form = RecipeForm(request.form)
    print add_form.errors

    if request.method == 'POST':
        print request
        if 'recipe-list-select' in request.form:
            recipes = select_form.recipes.data
            mms = MixMindServer(recipes=recipes, barstock_files=mms.barstock_files)
            flash("Now using recipes from {}".format(recipes))

    return render_template('recipes.html', select_form=select_form, add_form=add_form)

@app.route("/ingredients/", methods=['GET','POST'])
def ingredients():
    global mms
    form = BarstockForm(request.form)
    print form.errors

    if request.method == 'POST':
        print request
        if 'add-ingredient' in request.form:
            row = {}
            row['Category'] = form.category.data
            row['Type'] = form.type_.data
            row['Bottle'] = form.bottle.data
            row['Proof'] = float(form.proof.data)
            row['Size (mL)'] = float(form.size_ml.data)
            row['Price Paid'] = float(form.price.data)
            mms.barstock.add_row(row)

        elif 'remove-ingredient' in request.form:
            bottle = form.bottle.data
            if bottle in mms.barstock.df.Bottle.values:
                mms.barstock.df = mms.barstock.df[mms.barstock.df.Bottle != bottle]
                flash("Removed {}".format(bottle))
            else:
                flash("Error: \"{}\" not found; must match as shown below exactly".format(bottle))

        elif 'upload-csv' in request.form:
            filename = datafiles.save(request.files['upload_csv'])
            print "CSV uploaded to {}".format(filename)
            mms = MixMindServer(recipes=mms.recipe_files, barstock_files=[filename])
            flash("Ingredients database reloaded from {}".format(filename))

    table = mms.get_ingredients_table()
    return render_template('ingredients.html', form=form, table=table)

@app.route('/drinks.html')
def drinks_page():
    return app.send_static_file('drinks.html')

@app.route('/json/<recipe_name>')
def recipe_json(recipe_name):
    try:
        return str(mms.recipes[recipe_name])
    except KeyError:
        return "{} not found".format(recipe_name)

<|endoftext|>


<commit_before>"""Unit test module for portal views"""
from datetime import datetime
from tests import TestCase, TEST_USER_ID

from portal.extensions import db
from portal.models.role import ROLE
from portal.models.user import User
from portal.models.message import EmailInvite

from flask_swagger import swagger

class TestPortal(TestCase):
    """Portal view tests"""

    def test_admin_list(self):
        """Test admin view lists all users"""
        # Generate a few users with a smattering of roles
        u1 = self.add_user(username='u1')
        u2 = self.add_user(username='u2')
        self.promote_user(u1, role_name=ROLE.ADMIN)
        self.promote_user(u2, role_name=ROLE.APPLICATION_DEVELOPER)

        # Test user needs admin role to view list
        self.promote_user(role_name=ROLE.ADMIN)
        self.login()
        rv = self.app.get('/admin')

        # Should at least see an entry per user in system
        self.assertTrue(rv.data.count('/profile') >= User.query.count())

    def test_invite(self):
        """Test email invite form"""
        test_user = User.query.get(TEST_USER_ID)
        test_user.email = 'pbugni@uw.edu'
        db.session.add(test_user)
        db.session.commit()

        self.login()
        postdata = { 'subject': 'unittest subject',
                'recipients': 'bugni@yahoo.com pbugni@uw.edu',
                'body': "Ode to joy" }
        rv = self.app.post('/invite', data=postdata, follow_redirects=True)
        self.assertTrue("Email Invite Sent" in rv.data)

    def test_message_sent(self):
        """Email invites - test view for sent messages"""
        sent_at = datetime.strptime("2000/01/01 12:31:00",
                "%Y/%m/%d %H:%M:%S")
        message = EmailInvite(subject='a subject', user_id=TEST_USER_ID,
                sender="testuser@email.com",
                body='Welcome to testing', sent_at=sent_at,
                recipients="one@ex1.com two@two.org")
        db.session.add(message)
        db.session.commit()

        self.login()
        rv = self.app.get('/invite/{0}'.format(message.id))
        self.assertTrue(rv.data.find(sent_at.strftime('%m/%d/%Y %H:%M:%S'))
            > 0)
        self.assertTrue(rv.data.find('one@ex1.com two@two.org') > 0)

    def test_missing_message(self):
        """Request to view non existant message should 404"""
        self.login()
        rv = self.app.get('/invite/404')
        self.assertEquals(rv.status_code, 404)

    def test_celery_add(self):
        """Try simply add task handed off to celery"""
        x = 151
        y = 99
        rv = self.app.get('/celery-test?x={x}&y={y}&redirect-to-result=True'.\
                          format(x=x, y=y), follow_redirects=True)
        self.assert200(rv)
        self.assertEquals(rv.data, str(x + y))

    def test_swagger_docgen(self):
        """Build swagger docs for entire project"""

        swag = swagger(self.app.application)
        assert 'info' in swag
        assert 'paths' in swag
        assert 'swagger' in swag
        assert 'definitions' in swag
<commit_msg>Use assertIn() in Swagger docgen test<commit_after>"""Unit test module for portal views"""
from datetime import datetime
from tests import TestCase, TEST_USER_ID

from portal.extensions import db
from portal.models.role import ROLE
from portal.models.user import User
from portal.models.message import EmailInvite

from flask_swagger import swagger

class TestPortal(TestCase):
    """Portal view tests"""

    def test_admin_list(self):
        """Test admin view lists all users"""
        # Generate a few users with a smattering of roles
        u1 = self.add_user(username='u1')
        u2 = self.add_user(username='u2')
        self.promote_user(u1, role_name=ROLE.ADMIN)
        self.promote_user(u2, role_name=ROLE.APPLICATION_DEVELOPER)

        # Test user needs admin role to view list
        self.promote_user(role_name=ROLE.ADMIN)
        self.login()
        rv = self.app.get('/admin')

        # Should at least see an entry per user in system
        self.assertTrue(rv.data.count('/profile') >= User.query.count())

    def test_invite(self):
        """Test email invite form"""
        test_user = User.query.get(TEST_USER_ID)
        test_user.email = 'pbugni@uw.edu'
        db.session.add(test_user)
        db.session.commit()

        self.login()
        postdata = { 'subject': 'unittest subject',
                'recipients': 'bugni@yahoo.com pbugni@uw.edu',
                'body': "Ode to joy" }
        rv = self.app.post('/invite', data=postdata, follow_redirects=True)
        self.assertTrue("Email Invite Sent" in rv.data)

    def test_message_sent(self):
        """Email invites - test view for sent messages"""
        sent_at = datetime.strptime("2000/01/01 12:31:00",
                "%Y/%m/%d %H:%M:%S")
        message = EmailInvite(subject='a subject', user_id=TEST_USER_ID,
                sender="testuser@email.com",
                body='Welcome to testing', sent_at=sent_at,
                recipients="one@ex1.com two@two.org")
        db.session.add(message)
        db.session.commit()

        self.login()
        rv = self.app.get('/invite/{0}'.format(message.id))
        self.assertTrue(rv.data.find(sent_at.strftime('%m/%d/%Y %H:%M:%S'))
            > 0)
        self.assertTrue(rv.data.find('one@ex1.com two@two.org') > 0)

    def test_missing_message(self):
        """Request to view non existant message should 404"""
        self.login()
        rv = self.app.get('/invite/404')
        self.assertEquals(rv.status_code, 404)

    def test_celery_add(self):
        """Try simply add task handed off to celery"""
        x = 151
        y = 99
        rv = self.app.get('/celery-test?x={x}&y={y}&redirect-to-result=True'.\
                          format(x=x, y=y), follow_redirects=True)
        self.assert200(rv)
        self.assertEquals(rv.data, str(x + y))

    def test_swagger_docgen(self):
        """Build swagger docs for entire project"""

        expected_keys = (
            'info',
            'paths',
            'swagger',
            'definitions',
        )
        swag = swagger(self.app.application)

        for key in expected_keys:
            self.assertIn(key, swag)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
To check

+ styleitem auto - should be capitalised (layer)
+ Each of the units is different - [pixels|feet|inches|kilometers|meters|miles|nauticalmiles|dd] - standardise
"""
import logging
import json
import collections
import pytest
from mappyfile.parser import Parser
from mappyfile.pprint import PrettyPrinter, Quoter
from mappyfile.transformer import MapfileToDict
import mappyfile

def test_nested_quotes():
    r"""
    If values contain quotes then make sure they are escaped
    shp2img -m C:\Temp\msautotest\misc\ogr_vrtconnect.tmp.map
    """
    s = """
    LAYER
        NAME shppoly
        TYPE polygon
        CONNECTIONTYPE OGR
        CONNECTION '<OGRVRTDataSource><OGRVRTLayer name="poly"><SrcDataSource relativeToVRT="0">data/shppoly</SrcDataSource><SrcLayer>poly</SrcLayer></OGRVRTLayer></OGRVRTDataSource>'
    END"""

    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=0, quote='"', newlinechar=" ")  # expected
    res = pp.pprint(ast)
    print(res)
    exp = 'LAYER NAME "shppoly" TYPE POLYGON CONNECTIONTYPE OGR CONNECTION "<OGRVRTDataSource><OGRVRTLayer name="poly">' \
    '<SrcDataSource relativeToVRT="0">data/shppoly</SrcDataSource><SrcLayer>poly</SrcLayer></OGRVRTLayer></OGRVRTDataSource>" END'
    assert(res == exp)

def test_standardise_quotes():

    v = '"the_geom from (select * from road where "name_e"=\'Trans-Canada Highway\' order by gid) as foo using unique gid using srid=3978"'

    q = Quoter(quote='"')
    v2 = q.standardise_quotes(v)
    exp = r'''"the_geom from (select * from road where \"name_e\"='Trans-Canada Highway' order by gid) as foo using unique gid using srid=3978"'''
    assert(v2 == exp)

    q = Quoter(quote="'")
    v2 = q.standardise_quotes(v)
    exp = r"""'the_geom from (select * from road where "name_e"=\'Trans-Canada Highway\' order by gid) as foo using unique gid using srid=3978'"""
    assert(v2 == exp)

def test_already_escaped():
    """
    Don't escape an already escaped quote
    """
    s = r'CLASS EXPRESSION "\"Tignish" END'
    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=0, quote='"', newlinechar=" ")
    res = pp.pprint(ast)
    exp = r'CLASS EXPRESSION "\"Tignish" END'
    assert(res == exp)

    s = r"CLASS EXPRESSION '\'Tignish' END"
    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    res = pp.pprint(ast)
    exp = r"CLASS EXPRESSION '\'Tignish' END"
    assert(res == exp)

def test_unicode():

    s = u"""
    MAP
        METADATA
          "ows_title" "éúáí"
        END
    END
    """
    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    res = pp.pprint(ast)
    exp = u"MAP METADATA 'ows_title' 'éúáí' END END"
    assert(res == exp)

def test_enumeration():
    s = """
    MAP
        EXTENT 0 0 100 100
        DEBUG on
        NAME Test
        shapepath "test/path"
        LAYER
            CLASSITEM "Test"
            CLASS
                EXPRESSION "Field"
                STYLE
                    SIZE [sizefield]
                END
            END
        END
    END
    """
    p = Parser()
    m = MapfileToDict()
    ast = p.parse(s)
    d = m.transform(ast)
    print(d)
    logging.debug(json.dumps(d, indent=4))
    pp = PrettyPrinter(indent=4, quote="'", newlinechar="\n")
    res = pp.pprint(d)
    print(res)

def test_points():

    d = {
    "points": [[[1,
                1],
            [50,
                50],
            [1,
                50],
            [1,
                1]],
            [[100,
                100],
            [50,
                50],
            [100,
                50],
            [100,
                100]]],
    "__type__": "feature"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "FEATURE POINTS 1 1 50 50 1 50 1 1 END POINTS 100 100 50 50 100 50 100 100 END END")

def test_style_pattern():

    d = {
        "pattern": [[10,
                1],
            [50,
                50],
            [1,
                50],
            [1,
                1]],
        "__type__": "style"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    assert(s == "STYLE PATTERN 10 1 50 50 1 50 1 1 END END")

def test_scaletoken():

    sd = {
        "0": "ON",
        "255000000": "OFF"
    }

    sd = collections.OrderedDict(sorted(sd.items()))

    d = {
    "name": "%border%",
    "values": sd,
    "__type__": "scaletoken"
    }

    d = collections.OrderedDict(sorted(d.items()))

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "SCALETOKEN NAME '%border%' VALUES '0' 'ON' '255000000' 'OFF' END END")

def test_metadata():

    md = {
            "MS_ENABLE_MODES": "!*",
            "WMS_ENABLE_REQUEST": "*"
        }

    md = collections.OrderedDict(sorted(md.items()))

    d = {
        "metadata": md,
        "__type__": "map"
    }

    d = collections.OrderedDict(sorted(d.items()))

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP METADATA 'MS_ENABLE_MODES' '!*' 'WMS_ENABLE_REQUEST' '*' END END")

def test_config():

    cd = {
            "ms_nonsquare": "YES",
            "on_missing_data": "FAIL"
          }

    cd = collections.OrderedDict(sorted(cd.items()))

    d = {
        "config": cd,
        "__type__": "map"
    }

    d = collections.OrderedDict(sorted(d.items()))

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP CONFIG 'MS_NONSQUARE' 'YES' CONFIG 'ON_MISSING_DATA' 'FAIL' END")

def test_processing():
    d = {
    "name": "ProcessingLayer",
    "processing": ["BANDS=1",
        "CONTOUR_ITEM=elevation",
        "CONTOUR_INTERVAL=20"],
    "__type__": "layer"
    }

    d = collections.OrderedDict(sorted(d.items()))

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "LAYER NAME 'ProcessingLayer' PROCESSING 'BANDS=1' PROCESSING 'CONTOUR_ITEM=elevation' PROCESSING 'CONTOUR_INTERVAL=20' END")

def test_multiple_layers():
    d = {
    "layers": [{
            "name": "Layer1",
            "__type__": "layer"
        },
        {
            "name": "Layer2",
            "__type__": "layer"
        }],
    "__type__": "map"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP LAYER NAME 'Layer1' END LAYER NAME 'Layer2' END END")

def test_projection():
    d = {
    "projection": ["proj=utm",
        "ellps=GRS80",
        "datum=NAD83",
        "zone=15",
        "units=m",
        "north",
        "no_defs"],
    "__type__": "map"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP PROJECTION 'proj=utm' 'ellps=GRS80' 'datum=NAD83' 'zone=15' 'units=m' 'north' 'no_defs' END END")

def test_auto_projection():
    d = {
    "projection": ["auto"],
    "__type__": "map"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP PROJECTION AUTO END END")

def test_single_string_projection():
    d = {
    "projection": "init=epsg:4326",
    "__type__": "map"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP PROJECTION 'init=epsg:4326' END END")

def test_print_boolean():
    d = {
        "transform": True,
        "__type__": "layer"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "LAYER TRANSFORM TRUE END")

def test_class_list():

    d1 = {
            "text": "([area])",
            "expression": "([area])",
            "__type__": "class"
         }
    d2 = {
            "text": "(\"[area]\")",
            "expression": "(\"[area]\")",
            "__type__": "class"
         }

    d1 = collections.OrderedDict(sorted(d1.items()))
    d2 = collections.OrderedDict(sorted(d2.items()))

    classes = [d1, d2]

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(classes)
    print(s)
    assert(s == 'CLASS EXPRESSION ([area]) TEXT ([area]) END CLASS EXPRESSION ("[area]") TEXT ("[area]") END')

def test_get_attribute_properties():
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("layer", "connectiontype")
    print(props)
    assert(len(props["enum"]) == 13)

def test_get_ref_attribute_properties():
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("layer", "labelcache")
    print(props)
    assert(len(props["enum"]) == 2)

def test_get_label_shadowcolor_properties():
    # check a single ref is resolved
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("label", "shadowcolor")
    print(props)
    assert(len(props["oneOf"]) == 2)

def test_get_label_position_properties():
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("label", "position")
    assert(props["oneOf"][1]["enum"] == [u'ul', u'uc', u'ur', u'cl', u'cc', u'cr', u'll', u'lc', u'lr'])

def test_map_layers_props():
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("map", "layers")
    print(props)
    # assert(props["type"] == "object")
    assert(props["type"] == "array")

def test_end_comment():
    s = "MAP LAYER TYPE POINT NAME 'Test' END END"
    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=4, quote='"', newlinechar="\n", end_comment=True)
    res = pp.pprint(ast)
    print(res)
    exp = """MAP
    LAYER
        TYPE POINT
        NAME "Test"
    END # LAYER
END # MAP"""
    assert res == exp

def run_tests():
    # pytest.main(["tests/test_pprint.py::test_format_list"])
    pytest.main(["tests/test_pprint.py"])

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    test_end_comment()
    # run_tests()
    print("Done!")
<commit_msg>Add join test<commit_after># -*- coding: utf-8 -*-
"""
To check

+ styleitem auto - should be capitalised (layer)
+ Each of the units is different - [pixels|feet|inches|kilometers|meters|miles|nauticalmiles|dd] - standardise
"""
import logging
import json
import collections
import pytest
from mappyfile.parser import Parser
from mappyfile.pprint import PrettyPrinter, Quoter
from mappyfile.transformer import MapfileToDict
import mappyfile

def test_nested_quotes():
    r"""
    If values contain quotes then make sure they are escaped
    shp2img -m C:\Temp\msautotest\misc\ogr_vrtconnect.tmp.map
    """
    s = """
    LAYER
        NAME shppoly
        TYPE polygon
        CONNECTIONTYPE OGR
        CONNECTION '<OGRVRTDataSource><OGRVRTLayer name="poly"><SrcDataSource relativeToVRT="0">data/shppoly</SrcDataSource><SrcLayer>poly</SrcLayer></OGRVRTLayer></OGRVRTDataSource>'
    END"""

    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=0, quote='"', newlinechar=" ")  # expected
    res = pp.pprint(ast)
    print(res)
    exp = 'LAYER NAME "shppoly" TYPE POLYGON CONNECTIONTYPE OGR CONNECTION "<OGRVRTDataSource><OGRVRTLayer name="poly">' \
    '<SrcDataSource relativeToVRT="0">data/shppoly</SrcDataSource><SrcLayer>poly</SrcLayer></OGRVRTLayer></OGRVRTDataSource>" END'
    assert(res == exp)

def test_standardise_quotes():

    v = '"the_geom from (select * from road where "name_e"=\'Trans-Canada Highway\' order by gid) as foo using unique gid using srid=3978"'

    q = Quoter(quote='"')
    v2 = q.standardise_quotes(v)
    exp = r'''"the_geom from (select * from road where \"name_e\"='Trans-Canada Highway' order by gid) as foo using unique gid using srid=3978"'''
    assert(v2 == exp)

    q = Quoter(quote="'")
    v2 = q.standardise_quotes(v)
    exp = r"""'the_geom from (select * from road where "name_e"=\'Trans-Canada Highway\' order by gid) as foo using unique gid using srid=3978'"""
    assert(v2 == exp)

def test_already_escaped():
    """
    Don't escape an already escaped quote
    """
    s = r'CLASS EXPRESSION "\"Tignish" END'
    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=0, quote='"', newlinechar=" ")
    res = pp.pprint(ast)
    exp = r'CLASS EXPRESSION "\"Tignish" END'
    assert(res == exp)

    s = r"CLASS EXPRESSION '\'Tignish' END"
    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    res = pp.pprint(ast)
    exp = r"CLASS EXPRESSION '\'Tignish' END"
    assert(res == exp)

def test_unicode():

    s = u"""
    MAP
        METADATA
          "ows_title" "éúáí"
        END
    END
    """
    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    res = pp.pprint(ast)
    exp = u"MAP METADATA 'ows_title' 'éúáí' END END"
    assert(res == exp)

def test_enumeration():
    s = """
    MAP
        EXTENT 0 0 100 100
        DEBUG on
        NAME Test
        shapepath "test/path"
        LAYER
            CLASSITEM "Test"
            CLASS
                EXPRESSION "Field"
                STYLE
                    SIZE [sizefield]
                END
            END
        END
    END
    """
    p = Parser()
    m = MapfileToDict()
    ast = p.parse(s)
    d = m.transform(ast)
    print(d)
    logging.debug(json.dumps(d, indent=4))
    pp = PrettyPrinter(indent=4, quote="'", newlinechar="\n")
    res = pp.pprint(d)
    print(res)

def test_points():

    d = {
    "points": [[[1,
                1],
            [50,
                50],
            [1,
                50],
            [1,
                1]],
            [[100,
                100],
            [50,
                50],
            [100,
                50],
            [100,
                100]]],
    "__type__": "feature"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "FEATURE POINTS 1 1 50 50 1 50 1 1 END POINTS 100 100 50 50 100 50 100 100 END END")

def test_style_pattern():

    d = {
        "pattern": [[10,
                1],
            [50,
                50],
            [1,
                50],
            [1,
                1]],
        "__type__": "style"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    assert(s == "STYLE PATTERN 10 1 50 50 1 50 1 1 END END")

def test_scaletoken():

    sd = {
        "0": "ON",
        "255000000": "OFF"
    }

    sd = collections.OrderedDict(sorted(sd.items()))

    d = {
    "name": "%border%",
    "values": sd,
    "__type__": "scaletoken"
    }

    d = collections.OrderedDict(sorted(d.items()))

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "SCALETOKEN NAME '%border%' VALUES '0' 'ON' '255000000' 'OFF' END END")

def test_metadata():

    md = {
            "MS_ENABLE_MODES": "!*",
            "WMS_ENABLE_REQUEST": "*"
        }

    md = collections.OrderedDict(sorted(md.items()))

    d = {
        "metadata": md,
        "__type__": "map"
    }

    d = collections.OrderedDict(sorted(d.items()))

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP METADATA 'MS_ENABLE_MODES' '!*' 'WMS_ENABLE_REQUEST' '*' END END")

def test_config():

    cd = {
            "ms_nonsquare": "YES",
            "on_missing_data": "FAIL"
          }

    cd = collections.OrderedDict(sorted(cd.items()))

    d = {
        "config": cd,
        "__type__": "map"
    }

    d = collections.OrderedDict(sorted(d.items()))

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP CONFIG 'MS_NONSQUARE' 'YES' CONFIG 'ON_MISSING_DATA' 'FAIL' END")

def test_processing():
    d = {
    "name": "ProcessingLayer",
    "processing": ["BANDS=1",
        "CONTOUR_ITEM=elevation",
        "CONTOUR_INTERVAL=20"],
    "__type__": "layer"
    }

    d = collections.OrderedDict(sorted(d.items()))

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "LAYER NAME 'ProcessingLayer' PROCESSING 'BANDS=1' PROCESSING 'CONTOUR_ITEM=elevation' PROCESSING 'CONTOUR_INTERVAL=20' END")

def test_multiple_layers():
    d = {
    "layers": [{
            "name": "Layer1",
            "__type__": "layer"
        },
        {
            "name": "Layer2",
            "__type__": "layer"
        }],
    "__type__": "map"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP LAYER NAME 'Layer1' END LAYER NAME 'Layer2' END END")

def test_projection():
    d = {
    "projection": ["proj=utm",
        "ellps=GRS80",
        "datum=NAD83",
        "zone=15",
        "units=m",
        "north",
        "no_defs"],
    "__type__": "map"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP PROJECTION 'proj=utm' 'ellps=GRS80' 'datum=NAD83' 'zone=15' 'units=m' 'north' 'no_defs' END END")

def test_auto_projection():
    d = {
    "projection": ["auto"],
    "__type__": "map"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP PROJECTION AUTO END END")

def test_single_string_projection():
    d = {
    "projection": "init=epsg:4326",
    "__type__": "map"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "MAP PROJECTION 'init=epsg:4326' END END")

def test_print_boolean():
    d = {
        "transform": True,
        "__type__": "layer"
    }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "LAYER TRANSFORM TRUE END")

def test_join():
    d = {
        "__type__": "layer",
       "name": "Joined",
       "joins": [{
            "__type__": "join",
            "name": "table_join"
                }]
        }

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(d)
    print(s)
    assert(s == "LAYER NAME 'Joined' JOIN NAME 'table_join' END")

def test_class_list():

    d1 = {
            "text": "([area])",
            "expression": "([area])",
            "__type__": "class"
         }
    d2 = {
            "text": "(\"[area]\")",
            "expression": "(\"[area]\")",
            "__type__": "class"
         }

    d1 = collections.OrderedDict(sorted(d1.items()))
    d2 = collections.OrderedDict(sorted(d2.items()))

    classes = [d1, d2]

    pp = PrettyPrinter(indent=0, quote="'", newlinechar=" ")
    s = pp.pprint(classes)
    print(s)
    assert(s == 'CLASS EXPRESSION ([area]) TEXT ([area]) END CLASS EXPRESSION ("[area]") TEXT ("[area]") END')

def test_get_attribute_properties():
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("layer", "connectiontype")
    print(props)
    assert(len(props["enum"]) == 13)

def test_get_ref_attribute_properties():
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("layer", "labelcache")
    print(props)
    assert(len(props["enum"]) == 2)

def test_get_label_shadowcolor_properties():
    # check a single ref is resolved
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("label", "shadowcolor")
    print(props)
    assert(len(props["oneOf"]) == 2)

def test_get_label_position_properties():
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("label", "position")
    assert(props["oneOf"][1]["enum"] == [u'ul', u'uc', u'ur', u'cl', u'cc', u'cr', u'll', u'lc', u'lr'])

def test_map_layers_props():
    pp = PrettyPrinter()
    props = pp.get_attribute_properties("map", "layers")
    print(props)
    # assert(props["type"] == "object")
    assert(props["type"] == "array")

def test_end_comment():
    s = "MAP LAYER TYPE POINT NAME 'Test' END END"
    ast = mappyfile.loads(s)
    pp = PrettyPrinter(indent=4, quote='"', newlinechar="\n", end_comment=True)
    res = pp.pprint(ast)
    print(res)
    exp = """MAP
    LAYER
        TYPE POINT
        NAME "Test"
    END # LAYER
END # MAP"""
    assert res == exp

def run_tests():
    # pytest.main(["tests/test_pprint.py::test_format_list"])
    pytest.main(["tests/test_pprint.py"])

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    test_join()
    # run_tests()
    print("Done!")
<|endoftext|>


<commit_before>from collections import Counter
from operator import itemgetter

import numpy as np
from numpy import linalg as LA

from scipy.spatial import distance

from mlfs.supervised.base import IModel

class KNearestNeighbors(IModel):

    def __init__(self, k=1, distance='L2'):
        # Use L1 (taxicab distance) or L2 distance (Euclidean distance)
        distance = distance.upper()
        assert distance in {'L1', 'L2'}

        self._k = k
        self._distance = distance
        self._X_train = None
        self._y_train = None

    def fit(self, X_train, y_train):
        self._X_train = X_train
        self._y_train = y_train

    def predict(self, X_test):
        return np.array([self._predict(x) for x in X_test])

    def _predict(self, x):
        # Calculate the distance between `x` and each training sample
        distances = np.zeros(self._X_train.shape[0])
        for i, x_tr in enumerate(self._X_train):
            # Calculate the L1 or L2 distance using the definition of norm
            distances[i] = LA.norm(x - x_tr, ord=int(self._distance[1]))

        # Combine the distance of each training sample to its label
        neighbors = zip(distances, self._y_train)
        # Get the labels of the k nearest ones
        k_nearest_neighbors = sorted(neighbors, key=itemgetter(0))[:self._k]
        k_nearest_labels = [x[1] for x in k_nearest_neighbors]
        # The prediction is calculated through a majority vote
        prediction = Counter(k_nearest_labels).most_common(1)[0][0]

        return prediction

    def evaluate(self, X_test, y_test):
        predictions = self.predict(X_test)
        accuracy = np.mean(predictions == y_test)

        return {'accuracy': accuracy}
<commit_msg>Added demo for KNN<commit_after>from collections import Counter
from operator import itemgetter

import numpy as np
from numpy import linalg as LA

from scipy.spatial import distance

import fire
from sklearn.datasets import load_digits

from mlfs import datasets
from mlfs.supervised.base import IModel

class KNearestNeighbors(IModel):

    def __init__(self, k=1, distance='L2'):
        # Use L1 (taxicab distance) or L2 distance (Euclidean distance)
        distance = distance.upper()
        assert distance in {'L1', 'L2'}

        self._k = k
        self._distance = distance
        self._X_train = None
        self._y_train = None

    def fit(self, X_train, y_train):
        self._X_train = X_train
        self._y_train = y_train

    def predict(self, X_test):
        return np.array([self._predict(x) for x in X_test])

    def _predict(self, x):
        # Calculate the distance between `x` and each training sample
        distances = np.zeros(self._X_train.shape[0])
        for i, x_tr in enumerate(self._X_train):
            # Calculate the L1 or L2 distance using the definition of norm
            distances[i] = LA.norm(x - x_tr, ord=int(self._distance[1]))

        # Combine the distance of each training sample to its label
        neighbors = zip(distances, self._y_train)
        # Get the labels of the k nearest ones
        k_nearest_neighbors = sorted(neighbors, key=itemgetter(0))[:self._k]
        k_nearest_labels = [x[1] for x in k_nearest_neighbors]
        # The prediction is calculated through a majority vote
        prediction = Counter(k_nearest_labels).most_common(1)[0][0]

        return prediction

    def evaluate(self, X_test, y_test):
        predictions = self.predict(X_test)
        accuracy = np.mean(predictions == y_test)

        return {'accuracy': accuracy}

class KNearestNeighborsDemo(object):

    def run(self, k=1, distance='L2', training_set_percentage=80, seed=0):
        mnist = load_digits()
        ds = datasets.split(mnist, training_set_percentage, seed=seed)

        m = KNearestNeighbors(k=k, distance=distance)
        m.fit(ds['training_set']['data'], ds['training_set']['target'])
        evaluation = m.evaluate(ds['test_set']['data'],
                                ds['test_set']['target'])

        return evaluation

if __name__ == '__main__':
    fire.Fire(KNearestNeighborsDemo)
<|endoftext|>


<commit_before>from ref import backend_to_check, check_import

ok = False
if check_import("gi"):
    import gi
    # Arch: AttributeError: module 'gi' has no attribute 'require_version'
    try:
        gi.require_version
        ok = True
    except AttributeError:
        pass
if ok:

    def test_pygdk3():
        backend_to_check("pygdk3")
<commit_msg>test: check gdk3 import<commit_after>from pyscreenshot.util import use_x_display

from ref import backend_to_check, check_import

ok = False
if check_import("gi"):
    if use_x_display():
        import gi

        # Arch: AttributeError: module 'gi' has no attribute 'require_version'
        try:
            gi.require_version
            ok = True
        except AttributeError:
            pass
if ok:

    def test_pygdk3():
        backend_to_check("pygdk3")
<|endoftext|>


<commit_before>#!/usr/bin/python3
"""
A robot that downloads stats for MNL creates an image to send to slack.
Intended to hurt diques feelings
F#&$ You, Kevan
Wow this stopped working
"""
import argparse
import csv
import datetime
import io

import logging
import math
from collections import OrderedDict
import os

try:
    import matplotlib.pyplot as plt
except ImportError as e:
    print(e)
    plt = None

import requests
from slack import WebClient

VELOCITY = 13
SHOOTOUT = 0.6
OVERTIME = 0.8

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)

SLACK_CLIENT_ID = os.environ.get('SLACK_CLIENT_ID')
SLACK_CLIENT = WebClient(SLACK_CLIENT_ID)
IMGUR_CLIENT_ID = os.environ.get('IMGUR_CLIENT_ID')
CSV_ID = "1txfiFqoekWQFO1yJDyV1sgTzereo_cNBm-V6OIJ0Nww"

class Team:
    """
    Model to hold ELO histories of each team
    """
    def __init__(self, name, color, emoji):
        self.name = name
        self.history = [1500]
        self.color = color
        self.emoji = emoji

    @property
    def elo(self):
        return self.history[-1]

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.name

    def win(self, change):
        self.history.append(self.elo + change)

    def lose(self, change):
        self.history.append(self.elo - change)

    def bye_week(self):
        self.history.append(self.elo)

    @property
    def latest_change(self):
        if len(self.history) < 2:
            return 0.0
        return self.elo - self.history[-2]

    def last_game_explanation(self):
        if self.latest_change > 0:
            return f"{self.name} rating goes up to {self.elo:.1f} ({self.latest_change:.1f})"
        elif self.latest_change < 0:
            return f"{self.name} rating goes down to {self.elo:.1f} ({self.latest_change:.1f})"
        else:
            return f"{self.name} rating is at {self.elo:.1f}"

TEAMS = {team.name: team for team in [
    Team("Americans", "#FF0000", ":america:"),
    Team("Tigers", "#F1C232", ":tigers:"),
    Team("Maroons", "#660000", ":maroons:"),
    Team("North Stars", "#6AA84F", ":north_stars:"),
    Team("Golden Seals", "#000000", ":seals:"),
    Team("Whalers", "#0000FF", ":whalers:"),
    Team("Nordiques", "#999999", ":nordiques:")
]}

def get_score(score):
    """
    Get the score in the from the csv string
    """
    return int(score.split()[0])

def get_shootout(row):
    """
    Whether or not the game was decided in a shootout
    """
    return ('SO' in row['Home Score']) or ('SO' in row['Away Score'])

def get_overtime(row):
    """
    Whether or not the game was decided in overtime
    """
    return ('OT' in row['Home Score']) or ('OT' in row['Away Score'])

def get_outcome(home_team_score, away_team_score, overtime, shootout):
    """
    Gets the actual value of the game
    """
    if not shootout and home_team_score > away_team_score:
        return 1
    elif overtime and home_team_score > away_team_score:
        return OVERTIME
    elif overtime and away_team_score > home_team_score:
        return 1 - OVERTIME
    elif shootout and home_team_score > away_team_score:
        return SHOOTOUT
    elif shootout and away_team_score > home_team_score:
        return 1 - SHOOTOUT
    else:
        return 0

def get_expected(home_team, away_team):
    """
    get the expected value of a game
    """
    return 1.0 / (1.0 + 10.0 ** (-(home_team.elo - away_team.elo)/400))

def get_margin(home_team, away_team, home_team_score, away_team_score):
    """
    Get the multiplier for the margin of victory
    """
    goal_differential = home_team_score - away_team_score
    return max(1, math.log(
        abs(goal_differential - .85 * ((home_team.elo - away_team.elo)/100)) + math.e - 1)
    )

def set_elos(winner, loser, change, winner_score, loser_score, overtime, shootout):
    ot_so = ""
    if overtime:
        ot_so = "(OT)"
    elif shootout:
        ot_so = "(SO)"

    LOGGER.info(f"""{winner.name} {winner_score} {loser.name} {loser_score} {ot_so}.
    {winner.name} elo {winner.elo:.1f} + {change:.1f}
    {loser.name} elo {loser.elo:.1f} - {change:.1f}""")

    winner.win(change)
    loser.lose(change)

def process_game(row):
    home_team = TEAMS[row['Home Team']]
    away_team = TEAMS[row['Away Team']]
    home_team_score = get_score(row['Home Score'])
    away_team_score = get_score(row['Away Score'])
    overtime = get_overtime(row)
    shootout = get_shootout(row)
    margin = get_margin(home_team, away_team, home_team_score, away_team_score)
    outcome = get_outcome(home_team_score, away_team_score, overtime, shootout)
    expected = get_expected(home_team, away_team)
    change = VELOCITY * margin * (outcome - expected)

    if home_team_score > away_team_score:
        set_elos(home_team, away_team, change,
                 home_team_score, away_team_score,
                 overtime, shootout)
    elif away_team_score > home_team_score:
        set_elos(away_team, home_team, -change,
                 away_team_score, home_team_score,
                 overtime, shootout)
    else:
        raise Exception('THERE ARE NO TIES')

    return home_team, away_team

def print_elos(on_date, message):
    print(get_print_message(on_date, message))

def plot_elos():
    """
    Returns an in memory PNG of the picture of our teams ratings
    """
    assert plt is not None, "Matplotlib was not able to be imported"
    legend = []
    sorted_teams = OrderedDict(sorted(TEAMS.items(), key=lambda t: -t[1].elo))
    colors = [team.color for team in sorted_teams.values()]

    plt.gca().set_prop_cycle('color', colors)

    plt.title(f"MNL Elo, Velocity:{VELOCITY} OT:{OVERTIME} SO:{SHOOTOUT}")
    for team in sorted_teams.values():
        plt.plot(range(len(team.history)), team.history)
        legend.append(f"{team.name}: {team.elo:.1f}")
    plt.xticks(range(len(team.history)))
    plt.legend(legend, loc='upper left')
    buf = io.BytesIO()
    plt.savefig(buf)
    buf.seek(0)
    return buf

def get_print_message(on, message):
    winner_icons = []
    message += f"MNL Elo ratings for {on:%m/%d/%Y}\n"
    for team in TEAMS.values():
        if team.latest_change > 0:
            winner_icons.append(team.emoji)

    message += "  ".join(winner_icons)
    message += "\n"
    sorted_teams = OrderedDict(sorted(TEAMS.items(), key=lambda t: -t[1].elo))
    for team in sorted_teams.values():
        message += team.last_game_explanation()+"\n"

    return message

def post_elos_to_slack(link, on, channel="tests", message=""):
    SLACK_CLIENT.chat_postMessage(
        channel=channel,
        text=get_print_message(on, message),
        attachments=[
            {
                "image_url": link,
                "title": "Current Elo ratings"
            }
        ],
        as_user=True)

def upload_picture_to_imgur(image):
    response = requests.post(
        "https://api.imgur.com/3/image",
        files={"image": image},
        headers={
            "Authorization": f"Client-ID {IMGUR_CLIENT_ID}"
        }
    )
    if response.status_code == 200:
        return response.json()['data']['link']
    else:
        raise Exception(f"Couldn't upload picture response \n{response.content.decode()}")

def get_raw_results_reader():
    response = requests.get(
        f"https://docs.google.com/spreadsheets/d/{CSV_ID}/export?format=csv&gid=834633730"

    )
    buf = io.StringIO()
    buf.write(response.content.decode())
    buf.seek(0)
    return csv.DictReader(buf)

def process_results(results):
    """
    Populate the TEAMS dictionary with the ELOS after the results of each game.
    returns the date of the latest game played
    """
    games = 0
    weekly_teams_played = []
    last_game_date = None
    for row in results:
        try:
            if not row.get('Home Team'):  # bye week
                continue
            weekly_teams_played.extend(process_game(row))
            games += 1
        except IndexError:
            break
        except KeyError:
            continue

        if games % 3 == 0: # finished the week
            last_game_date = row['Date']
            odd_team_out = list(set(TEAMS.values()) - set(weekly_teams_played))[0]
            odd_team_out.bye_week()
            weekly_teams_played = []

    return datetime.datetime.strptime(last_game_date, "%m/%d/%Y")

if __name__ == '__main__':
    PARSER = argparse.ArgumentParser(
        description=("Download latest MNL results and calculate ratings and post to slack"))
    PARSER.add_argument('--post', action='store_true')
    PARSER.add_argument('--channel', default="tests")
    PARSER.add_argument('--message', default="")

    ARGS = PARSER.parse_args()
    last = process_results(get_raw_results_reader())
    image = plot_elos()
    if ARGS.post:
        link = upload_picture_to_imgur(image)
        post_elos_to_slack(link, last, ARGS.channel, ARGS.message)
    else:
        print_elos(last, ARGS.message)
<commit_msg>Update elo_bot.py (#15)<commit_after>#!/usr/bin/python3
"""
A robot that downloads stats for MNL creates an image to send to slack.
Intended to hurt diques feelings
F#&$ You, Kevan
Wow this stopped working... nvm, it's working again
"""
import argparse
import csv
import datetime
import io

import logging
import math
from collections import OrderedDict
import os

try:
    import matplotlib.pyplot as plt
except ImportError as e:
    print(e)
    plt = None

import requests
from slack import WebClient

VELOCITY = 13
SHOOTOUT = 0.6
OVERTIME = 0.8

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)

SLACK_CLIENT_ID = os.environ.get('SLACK_CLIENT_ID')
SLACK_CLIENT = WebClient(SLACK_CLIENT_ID)
IMGUR_CLIENT_ID = os.environ.get('IMGUR_CLIENT_ID')
CSV_ID = "1txfiFqoekWQFO1yJDyV1sgTzereo_cNBm-V6OIJ0Nww"

class Team:
    """
    Model to hold ELO histories of each team
    """
    def __init__(self, name, color, emoji):
        self.name = name
        self.history = [1500]
        self.color = color
        self.emoji = emoji

    @property
    def elo(self):
        return self.history[-1]

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.name

    def win(self, change):
        self.history.append(self.elo + change)

    def lose(self, change):
        self.history.append(self.elo - change)

    def bye_week(self):
        self.history.append(self.elo)

    @property
    def latest_change(self):
        if len(self.history) < 2:
            return 0.0
        return self.elo - self.history[-2]

    def last_game_explanation(self):
        if self.latest_change > 0:
            return f"{self.name} rating goes up to {self.elo:.1f} ({self.latest_change:.1f})"
        elif self.latest_change < 0:
            return f"{self.name} rating goes down to {self.elo:.1f} ({self.latest_change:.1f})"
        else:
            return f"{self.name} rating is at {self.elo:.1f}"

TEAMS = {team.name: team for team in [
    Team("Americans", "#FF0000", ":america:"),
    Team("Tigers", "#F1C232", ":tigers:"),
    Team("Maroons", "#660000", ":maroons:"),
    Team("North Stars", "#6AA84F", ":north_stars:"),
    Team("Golden Seals", "#000000", ":seals:"),
    Team("Whalers", "#0000FF", ":whalers:"),
    Team("Nordiques", "#999999", ":nordiques:")
]}

def get_score(score):
    """
    Get the score in the from the csv string
    """
    return int(score.split()[0])

def get_shootout(row):
    """
    Whether or not the game was decided in a shootout
    """
    return ('SO' in row['Home Score']) or ('SO' in row['Away Score'])

def get_overtime(row):
    """
    Whether or not the game was decided in overtime
    """
    return ('OT' in row['Home Score']) or ('OT' in row['Away Score'])

def get_outcome(home_team_score, away_team_score, overtime, shootout):
    """
    Gets the actual value of the game
    """
    if not shootout and home_team_score > away_team_score:
        return 1
    elif overtime and home_team_score > away_team_score:
        return OVERTIME
    elif overtime and away_team_score > home_team_score:
        return 1 - OVERTIME
    elif shootout and home_team_score > away_team_score:
        return SHOOTOUT
    elif shootout and away_team_score > home_team_score:
        return 1 - SHOOTOUT
    else:
        return 0

def get_expected(home_team, away_team):
    """
    get the expected value of a game
    """
    return 1.0 / (1.0 + 10.0 ** (-(home_team.elo - away_team.elo)/400))

def get_margin(home_team, away_team, home_team_score, away_team_score):
    """
    Get the multiplier for the margin of victory
    """
    goal_differential = home_team_score - away_team_score
    return max(1, math.log(
        abs(goal_differential - .85 * ((home_team.elo - away_team.elo)/100)) + math.e - 1)
    )

def set_elos(winner, loser, change, winner_score, loser_score, overtime, shootout):
    ot_so = ""
    if overtime:
        ot_so = "(OT)"
    elif shootout:
        ot_so = "(SO)"

    LOGGER.info(f"""{winner.name} {winner_score} {loser.name} {loser_score} {ot_so}.
    {winner.name} elo {winner.elo:.1f} + {change:.1f}
    {loser.name} elo {loser.elo:.1f} - {change:.1f}""")

    winner.win(change)
    loser.lose(change)

def process_game(row):
    home_team = TEAMS[row['Home Team']]
    away_team = TEAMS[row['Away Team']]
    home_team_score = get_score(row['Home Score'])
    away_team_score = get_score(row['Away Score'])
    overtime = get_overtime(row)
    shootout = get_shootout(row)
    margin = get_margin(home_team, away_team, home_team_score, away_team_score)
    outcome = get_outcome(home_team_score, away_team_score, overtime, shootout)
    expected = get_expected(home_team, away_team)
    change = VELOCITY * margin * (outcome - expected)

    if home_team_score > away_team_score:
        set_elos(home_team, away_team, change,
                 home_team_score, away_team_score,
                 overtime, shootout)
    elif away_team_score > home_team_score:
        set_elos(away_team, home_team, -change,
                 away_team_score, home_team_score,
                 overtime, shootout)
    else:
        raise Exception('THERE ARE NO TIES')

    return home_team, away_team

def print_elos(on_date, message):
    print(get_print_message(on_date, message))

def plot_elos():
    """
    Returns an in memory PNG of the picture of our teams ratings
    """
    assert plt is not None, "Matplotlib was not able to be imported"
    legend = []
    sorted_teams = OrderedDict(sorted(TEAMS.items(), key=lambda t: -t[1].elo))
    colors = [team.color for team in sorted_teams.values()]

    plt.gca().set_prop_cycle('color', colors)

    plt.title(f"MNL Elo, Velocity:{VELOCITY} OT:{OVERTIME} SO:{SHOOTOUT}")
    for team in sorted_teams.values():
        plt.plot(range(len(team.history)), team.history)
        legend.append(f"{team.name}: {team.elo:.1f}")
    plt.xticks(range(len(team.history)))
    plt.legend(legend, loc='upper left')
    buf = io.BytesIO()
    plt.savefig(buf)
    buf.seek(0)
    return buf

def get_print_message(on, message):
    winner_icons = []
    message += f"MNL Elo ratings for {on:%m/%d/%Y}\n"
    for team in TEAMS.values():
        if team.latest_change > 0:
            winner_icons.append(team.emoji)

    message += "  ".join(winner_icons)
    message += "\n"
    sorted_teams = OrderedDict(sorted(TEAMS.items(), key=lambda t: -t[1].elo))
    for team in sorted_teams.values():
        message += team.last_game_explanation()+"\n"

    return message

def post_elos_to_slack(link, on, channel="tests", message=""):
    SLACK_CLIENT.chat_postMessage(
        channel=channel,
        text=get_print_message(on, message),
        attachments=[
            {
                "image_url": link,
                "title": "Current Elo ratings"
            }
        ],
        as_user=True)

def upload_picture_to_imgur(image):
    response = requests.post(
        "https://api.imgur.com/3/image",
        files={"image": image},
        headers={
            "Authorization": f"Client-ID {IMGUR_CLIENT_ID}"
        }
    )
    if response.status_code == 200:
        return response.json()['data']['link']
    else:
        raise Exception(f"Couldn't upload picture response \n{response.content.decode()}")

def get_raw_results_reader():
    response = requests.get(
        f"https://docs.google.com/spreadsheets/d/{CSV_ID}/export?format=csv&gid=834633730"

    )
    buf = io.StringIO()
    buf.write(response.content.decode())
    buf.seek(0)
    return csv.DictReader(buf)

def process_results(results):
    """
    Populate the TEAMS dictionary with the ELOS after the results of each game.
    returns the date of the latest game played
    """
    games = 0
    weekly_teams_played = []
    last_game_date = None
    for row in results:
        try:
            if not row.get('Home Team'):  # bye week
                continue
            weekly_teams_played.extend(process_game(row))
            games += 1
        except IndexError:
            break
        except KeyError:
            continue

        if games % 3 == 0: # finished the week
            last_game_date = row['Date']
            odd_team_out = list(set(TEAMS.values()) - set(weekly_teams_played))[0]
            odd_team_out.bye_week()
            weekly_teams_played = []

    return datetime.datetime.strptime(last_game_date, "%m/%d/%Y")

if __name__ == '__main__':
    PARSER = argparse.ArgumentParser(
        description=("Download latest MNL results and calculate ratings and post to slack"))
    PARSER.add_argument('--post', action='store_true')
    PARSER.add_argument('--channel', default="tests")
    PARSER.add_argument('--message', default="")

    ARGS = PARSER.parse_args()
    last = process_results(get_raw_results_reader())
    image = plot_elos()
    if ARGS.post:
        link = upload_picture_to_imgur(image)
        post_elos_to_slack(link, last, ARGS.channel, ARGS.message)
    else:
        print_elos(last, ARGS.message)
<|endoftext|>