<commit_before>'''
dbkgroup (c) University of Manchester 2016

reaction-balancer is licensed under the MIT License.

To view a copy of this license, visit <http://opensource.org/licenses/MIT/>.

@author:  neilswainston
@author:  pauldobson
'''

from flask import Flask, request
from flask_restful import Resource, Api
from flask_cors import CORS, cross_origin
from libsbml import *
from subliminal import balance
import json

#Initialise app
app = Flask(__name__, static_url_path='')
CORS(app)
api = Api(app)

#----------------------------------------------------------
#Where the balancing hookup occurs
def list_balancer(listOfRxns):
	results = {}
	for id in listOfRxns:
		rxn = listOfRxns[id]
		try:
			is_balanced, was_balanced, balanced_rxn = balance.balance_reac(rxn,[('H', 1, 'h_balancer'), ('H2O', 0, 'h2o_balancer')])
			balanced_rxn = simplifier(balanced_rxn)
			if was_balanced:
				msg = 'already balanced'
			else:
				if is_balanced:
					msg = 'brought into balance'
				else:
					msg = 'not brought into balance'
			results[id] = [balanced_rxn,was_balanced,is_balanced,msg]
		except Exception, e:
			#print str(e)
			#traceback.print_exc()
			results[id] = [False, False, False, str(e)]
	return results

#Check minimal reaction stoichiometries are enforced
def simplifier(rxn):
	met_counter = {}
	met_tracker = {}
	#Roll through metabolites adding stoichiometries for equivalents
	for met in rxn:
		formula = met[0]
		charge = met[1]
		name = met[3]
		key = formula + "_" + str(charge) + "_" + name
		stoich = met[2]
		if key in met_counter:
			print 'Naughty! Balancing was required'
			val = met_counter[key]
			met_counter[key] = val + stoich
		else:
			met_counter[key] = stoich
		met_tracker[key] = [formula, charge, False, name]
	#Rebuild reaction
	new_reaction = []
	for met in met_tracker:
		val = met_tracker[met]
		stoich = met_counter[met]
		val[2] = stoich
		new_reaction.append(val)
	return new_reaction

#Extract species' formulae and charges
def species_details(model):
	speciesDict = {}
	listOfSpecies = model.getListOfSpecies()
	for species in listOfSpecies:
		id = species.getId()
		splugin = species.getPlugin("fbc")
		status = True
		#Get charge
		try:
			charge = splugin.getCharge()
		except:
			status = False
			charge = False
		#Get formula
		try:
			chemicalFormula = splugin.getChemicalFormula()
		except:
			status = False
			chemicalFormula = False
		#print id, [chemicalFormula, charge, status]
		speciesDict[id] = [chemicalFormula, charge, status]
	return speciesDict

#Convert SBML to service format
def sbml_to_serviceFormat(model):
	print 'sbml_to_service'
	#Get species
	speciesDict = species_details(model)

	#Get reactions
	processable_rxns = {}		#Can be sent to balancer
	lost_rxns = {}				#Cannot be sent to balancer (some components underspecified)
	listOfReactions = model.getListOfReactions()
	for rxn in listOfReactions:
		dense = []
		reactionStatus = True
		rid = rxn.getId()
		reactants = rxn.getListOfReactants() #list of speciesReferences
		for r in reactants:
			#Species id
			mid = r.getSpecies()
			#Pull species details
			details = speciesDict[mid]
			formula = details[0]
			charge = details[1]
			status = details[2]
			#Get stoichiometry
			stoich = r.getStoichiometry()
			if status:
				row = [formula,charge,-1*stoich,mid]
				dense.append(row)
			else:
				reactionStatus = False
		products = rxn.getListOfProducts()
		for p in products:
			#Species id
			mid = p.getSpecies()
			#Pull species details
			details = speciesDict[mid]
			formula = details[0]
			charge = details[1]
			status = details[2]
			#Get stoichiometry
			stoich = p.getStoichiometry()
			if status:
				row = [formula,charge,stoich,mid]
				dense.append(row)
			else:
				reactionStatus = False
		#Validate reaction
		if reactionStatus:
			processable_rxns[rid] = dense
		else:
			lost_rxns[rid] = False
	return processable_rxns, lost_rxns

#Convert service format to SBML
def serviceFormat_to_sbml(doc,processed,lost):
	print 'serviceFormat_to_sbml'
	model = doc.getModel()

	#Add balance compartment
	model.createCompartment()
	c1 = model.createCompartment()
	c1.setId('BALANCER')
	c1.setConstant(True)
	c1.setSize(1)
	c1.setSpatialDimensions(3)
	c1.setUnits('litre')

	#Add balance metabolites
	s1 = model.createSpecies()
	s1.setId('h2o_balancer')
	s1.setCompartment('BALANCER')
	s1.setConstant(False)
	s1.setInitialAmount(5)
	s1.setSubstanceUnits('mole')
	s1.setBoundaryCondition(True)
	s1.setHasOnlySubstanceUnits(False)
	splugin1 = s1.getPlugin("fbc")
	splugin1.setCharge(0)
	splugin1.setChemicalFormula("H2O")

	s2 = model.createSpecies()
	s2.setId('h_balancer')
	s2.setCompartment('BALANCER')
	s2.setConstant(False)
	s2.setInitialAmount(5)
	s2.setSubstanceUnits('mole')
	s2.setBoundaryCondition(True)
	s2.setHasOnlySubstanceUnits(False)
	splugin2 = s2.getPlugin("fbc")
	splugin2.setCharge(1)
	splugin2.setChemicalFormula("H")

	#Process reactions
	listOfReactions = model.getListOfReactions()
	for reaction in listOfReactions:
		rid = reaction.getId()
		#print 'Reaction ID:', rid
		#Have we processed this id?
		if rid in processed:
			#print 'Process', rid
			dense = processed[rid]
			rxn = dense[0]
			was_balanced = dense[1]
			is_balanced = dense[2]
			msg = dense[3]
			if was_balanced:
				note = 'AUTO-BALANCER: already balanced'
			else:
				if is_balanced:
					#Make a mini dictionary of results
					mini = {}
					for c in rxn:
						id = c[3]
						stoich = c[2]
						mini[id] = stoich
					#Push results into model
					reactants = reaction.getListOfReactants()
					for reactant in reactants:
						mid = reactant.getSpecies()
						new_stoich = mini[mid]
						reactant.setStoichiometry(new_stoich)
					products = reaction.getListOfProducts()
					for product in products:
						mid = product.getSpecies()
						new_stoich = mini[mid]
						product.setStoichiometry(new_stoich)
					note = 'AUTO-BALANCER: brought into balance'
				else:
					note = 'AUTO-BALANCER: not brought into balance'
		#Report that the balancer could not be applied
		else:
			#print 'Unassessed', rid
			note = 'AUTO-BALANCER: could not be assessed'

		#Update notes
		#print 'Set notes'
		if reaction.isSetNotes():
			reaction.appendNotes('
' + note + '
')
		else:
			reaction.setNotes('
' + note + '
')

	#print 'Write SBML'
	newSBML = writeSBMLToString(doc)
	#print 'Wrote SBML'

	return newSBML

#Run SBML balancer
def sbml_balancer(string):
	#Read SBML
	reader = SBMLReader()
	try:
		doc = reader.readSBMLFromString(string)
		print 'Successful readSBMLFromString'
	except:
		return 'Could not readSBMLFromString'

	#Check for errors
	errors = doc.getNumErrors();
	if doc.getNumErrors() > 0:
		if doc.getError(0).getErrorId() == XMLFileUnreadable:
			print 'Unreadable XML'
			return 'Unreadable XML'
		elif doc.getError(0).getErrorId() == XMLFileOperationError:
			print 'Operation error on XML'
			return 'Operation error on XML'
		else:
			print 'General cockup on the SBML reading front'
			return 'General cockup on the SBML reading front'

	#Get model
	model = doc.getModel()
	if model == None:
		print 'Unable to create model object'
		return 'Unable to create model object'

	#Check for fbc plugin
	mplugin = model.getPlugin("fbc")
	if mplugin == None:
		print 'Plugin error: the SBML service expects formulae and charges to be represented using the fbc package'
		return 'Plugin error: the SBML service expects formulae and charges to be represented using the fbc package'
	else:
		#Turn the SBML into service format
		try:
			processable, lost = sbml_to_serviceFormat(model)
			#print processable
			print len(processable), 'reactions can be processed'
			print len(lost), 'reactions cannot be processed'
			print 'Ran sbml_to_service'
		except:
			return 'Could not convert SBML'

		#Hit balancer
		try:
			processed = list_balancer(processable)
			#print processed
			print 'Hit list_balancer'
		except:
			return 'Problem hitting balancer'

		#Push results into model
		try:
			new_sbml = serviceFormat_to_sbml(doc,processed,lost)
			print 'Ran serviceFormat_to_sbml'
			response = new_sbml
			#print 'Processed:', processed
			return new_sbml
		except Exception as e:
			return 'Could not update SBML file with balance results', str(e)

	return False
#----------------------------------------------------------

#Endpoint for humans to read
#Landing page
@app.route('/')
def index():
	return app.send_static_file('index.html')

#Endpoint to be used by human UI
@app.route('/balance/manual', methods=['POST'])
def manual():
	print 'Human UI'
	content = request.get_json(force=True)
	tidyList = []
	for c in content:
		if str(c[0]) is not '':
			tidyComponent = [ str(c[0]), int(c[1]), float(c[2]), str(c[3]) ]
			tidyList.append(tidyComponent)
	if len(tidyList) > 0:
		serviceFormat = { "human" : tidyList}
		print 'Hitting balancer...'
		results = list_balancer(serviceFormat)
		print '...complete'

		print 'Converting result...'
		response = results["human"]
		reaction = response[0]
		was_balanced = response[1]
		is_balanced = response[2]
		msg = response[3]
		print reaction
		reactants = []
		products = []
		for component in reaction:
			print component
			formula = component[0]
			charge = component[1]
			stoich = int(component[2])
			name = component[3]
			if stoich < 0:
				stoich = stoich * -1
				elem = str(stoich) + '.' + name
				reactants.append(elem)
			else:
				elem = str(stoich) + '.' + name
				products.append(elem)
		reactantString = ' + '.join(reactants)
		productString = ' + '.join(products)
		reactionString = reactantString + '  ->  ' + productString
		print reactionString
		return reactionString
	else:
		return 'Nothing to balance'

#JSON endpoint
@app.route('/balance/json', methods=['POST'])
def list_json():
	listOfRxns = request.get_json(force=True)
	results = list_balancer(listOfRxns)
	return json.dumps(results)

#SBML endpoint
@api.representation('application/xml')
@app.route('/balance/SBML', methods=['POST'])
def sbml():
	try:
		sbmlString = request.data
	except:
		return 'Could not access SBML data'
	response = sbml_balancer(sbmlString)
	return response

if __name__ == '__main__':
	#main()
	app.run(host='0.0.0.0',debug=False, port=8080, threaded=True)<commit_msg>Add balancer metabolites to SBML<commit_after>'''
dbkgroup (c) University of Manchester 2016

reaction-balancer is licensed under the MIT License.

To view a copy of this license, visit <http://opensource.org/licenses/MIT/>.

@author:  neilswainston
@author:  pauldobson
'''

from flask import Flask, request
from flask_restful import Resource, Api
from flask_cors import CORS, cross_origin
from libsbml import *
from subliminal import balance
import json

#Initialise app
app = Flask(__name__, static_url_path='')
CORS(app)
api = Api(app)

#----------------------------------------------------------
#Where the balancing hookup occurs
def list_balancer(listOfRxns):
	results = {}
	for id in listOfRxns:
		rxn = listOfRxns[id]
		try:
			is_balanced, was_balanced, balanced_rxn = balance.balance_reac(rxn,[('H', 1, 'h_balancer'), ('H2O', 0, 'h2o_balancer')])
			balanced_rxn = simplifier(balanced_rxn)
			if was_balanced:
				msg = 'already balanced'
			else:
				if is_balanced:
					msg = 'brought into balance'
				else:
					msg = 'not brought into balance'
			results[id] = [balanced_rxn,was_balanced,is_balanced,msg]
		except Exception, e:
			#print str(e)
			#traceback.print_exc()
			results[id] = [False, False, False, str(e)]
	return results

#Check minimal reaction stoichiometries are enforced
def simplifier(rxn):
	met_counter = {}
	met_tracker = {}
	#Roll through metabolites adding stoichiometries for equivalents
	for met in rxn:
		formula = met[0]
		charge = met[1]
		name = met[3]
		key = formula + "_" + str(charge) + "_" + name
		stoich = met[2]
		if key in met_counter:
			print 'Naughty! Balancing was required'
			val = met_counter[key]
			met_counter[key] = val + stoich
		else:
			met_counter[key] = stoich
		met_tracker[key] = [formula, charge, False, name]
	#Rebuild reaction
	new_reaction = []
	for met in met_tracker:
		val = met_tracker[met]
		stoich = met_counter[met]
		val[2] = stoich
		new_reaction.append(val)
	return new_reaction

#Extract species' formulae and charges
def species_details(model):
	speciesDict = {}
	listOfSpecies = model.getListOfSpecies()
	for species in listOfSpecies:
		id = species.getId()
		splugin = species.getPlugin("fbc")
		status = True
		#Get charge
		try:
			charge = splugin.getCharge()
		except:
			status = False
			charge = False
		#Get formula
		try:
			chemicalFormula = splugin.getChemicalFormula()
		except:
			status = False
			chemicalFormula = False
		#print id, [chemicalFormula, charge, status]
		speciesDict[id] = [chemicalFormula, charge, status]
	return speciesDict

#Convert SBML to service format
def sbml_to_serviceFormat(model):
	print 'sbml_to_service'
	#Get species
	speciesDict = species_details(model)

	#Get reactions
	processable_rxns = {}		#Can be sent to balancer
	lost_rxns = {}				#Cannot be sent to balancer (some components underspecified)
	listOfReactions = model.getListOfReactions()
	for rxn in listOfReactions:
		dense = []
		reactionStatus = True
		rid = rxn.getId()
		reactants = rxn.getListOfReactants() #list of speciesReferences
		for r in reactants:
			#Species id
			mid = r.getSpecies()
			#Pull species details
			details = speciesDict[mid]
			formula = details[0]
			charge = details[1]
			status = details[2]
			#Get stoichiometry
			stoich = r.getStoichiometry()
			if status:
				row = [formula,charge,-1*stoich,mid]
				dense.append(row)
			else:
				reactionStatus = False
		products = rxn.getListOfProducts()
		for p in products:
			#Species id
			mid = p.getSpecies()
			#Pull species details
			details = speciesDict[mid]
			formula = details[0]
			charge = details[1]
			status = details[2]
			#Get stoichiometry
			stoich = p.getStoichiometry()
			if status:
				row = [formula,charge,stoich,mid]
				dense.append(row)
			else:
				reactionStatus = False
		#Validate reaction
		if reactionStatus:
			processable_rxns[rid] = dense
		else:
			lost_rxns[rid] = False
	return processable_rxns, lost_rxns

#Convert service format to SBML
def serviceFormat_to_sbml(doc,processed,lost):
	print 'serviceFormat_to_sbml'
	model = doc.getModel()

	#Add balance compartment
	model.createCompartment()
	c1 = model.createCompartment()
	c1.setId('BALANCER')
	c1.setConstant(True)
	c1.setSize(1)
	c1.setSpatialDimensions(3)
	c1.setUnits('litre')

	#Add balance metabolites
	s1 = model.createSpecies()
	s1.setId('h2o_balancer')
	s1.setCompartment('BALANCER')
	s1.setConstant(False)
	s1.setInitialAmount(5)
	s1.setSubstanceUnits('mole')
	s1.setBoundaryCondition(True)
	s1.setHasOnlySubstanceUnits(False)
	splugin1 = s1.getPlugin("fbc")
	splugin1.setCharge(0)
	splugin1.setChemicalFormula("H2O")

	s2 = model.createSpecies()
	s2.setId('h_balancer')
	s2.setCompartment('BALANCER')
	s2.setConstant(False)
	s2.setInitialAmount(5)
	s2.setSubstanceUnits('mole')
	s2.setBoundaryCondition(True)
	s2.setHasOnlySubstanceUnits(False)
	splugin2 = s2.getPlugin("fbc")
	splugin2.setCharge(1)
	splugin2.setChemicalFormula("H")

	#Process reactions
	listOfReactions = model.getListOfReactions()
	for reaction in listOfReactions:
		rid = reaction.getId()
		#print 'Reaction ID:', rid
		#Have we processed this id?
		if rid in processed:
			#print 'Process', rid
			dense = processed[rid]
			rxn = dense[0]
			was_balanced = dense[1]
			is_balanced = dense[2]
			msg = dense[3]
			if was_balanced:
				note = 'AUTO-BALANCER: already balanced'
			else:
				if is_balanced:
					#Make a mini dictionary of results and add in balancer metabolites
					mini = {}
					for c in rxn:
						id = c[3]
						stoich = c[2]
						mini[id] = stoich
						if id is 'h2o_balancer':
							if stoich < 0:
								reactant = reaction.createReactant()
								reactant.setSpecies("h2o_balancer")
								reactant.setStoichiometry(-1*stoich)
								reactant.setConstant(False)
							else:
								product = reaction.createProduct()
								product.setSpecies("h2o_balancer")
								product.setStoichiometry(stoich)
								product.setConstant(False)
						if id is 'h_balancer':
							if stoich < 0:
								reactant = reaction.createReactant()
								reactant.setSpecies("h_balancer")
								reactant.setStoichiometry(-1*stoich)
								reactant.setConstant(False)
							else:
								product = reaction.createProduct()
								product.setSpecies("h_balancer")
								product.setStoichiometry(stoich)
								product.setConstant(False)

					#Push results into model
					reactants = reaction.getListOfReactants()
					for reactant in reactants:
						mid = reactant.getSpecies()
						new_stoich = mini[mid]
						reactant.setStoichiometry(new_stoich)
					products = reaction.getListOfProducts()
					for product in products:
						mid = product.getSpecies()
						new_stoich = mini[mid]
						product.setStoichiometry(new_stoich)
					note = 'AUTO-BALANCER: brought into balance'
				else:
					note = 'AUTO-BALANCER: not brought into balance'
		#Report that the balancer could not be applied
		else:
			#print 'Unassessed', rid
			note = 'AUTO-BALANCER: could not be assessed'

		#Update notes
		#print 'Set notes'
		if reaction.isSetNotes():
			reaction.appendNotes('
' + note + '
')
		else:
			reaction.setNotes('
' + note + '
')

	#print 'Write SBML'
	newSBML = writeSBMLToString(doc)
	#print 'Wrote SBML'

	return newSBML

#Run SBML balancer
def sbml_balancer(string):
	#Read SBML
	reader = SBMLReader()
	try:
		doc = reader.readSBMLFromString(string)
		print 'Successful readSBMLFromString'
	except:
		return 'Could not readSBMLFromString'

	#Check for errors
	errors = doc.getNumErrors();
	if doc.getNumErrors() > 0:
		if doc.getError(0).getErrorId() == XMLFileUnreadable:
			print 'Unreadable XML'
			return 'Unreadable XML'
		elif doc.getError(0).getErrorId() == XMLFileOperationError:
			print 'Operation error on XML'
			return 'Operation error on XML'
		else:
			print 'General cockup on the SBML reading front'
			return 'General cockup on the SBML reading front'

	#Get model
	model = doc.getModel()
	if model == None:
		print 'Unable to create model object'
		return 'Unable to create model object'

	#Check for fbc plugin
	mplugin = model.getPlugin("fbc")
	if mplugin == None:
		print 'Plugin error: the SBML service expects formulae and charges to be represented using the fbc package'
		return 'Plugin error: the SBML service expects formulae and charges to be represented using the fbc package'
	else:
		#Turn the SBML into service format
		try:
			processable, lost = sbml_to_serviceFormat(model)
			#print processable
			print len(processable), 'reactions can be processed'
			print len(lost), 'reactions cannot be processed'
			print 'Ran sbml_to_service'
		except:
			return 'Could not convert SBML'

		#Hit balancer
		try:
			processed = list_balancer(processable)
			#print processed
			print 'Hit list_balancer'
		except:
			return 'Problem hitting balancer'

		#Push results into model
		try:
			new_sbml = serviceFormat_to_sbml(doc,processed,lost)
			print 'Ran serviceFormat_to_sbml'
			response = new_sbml
			#print 'Processed:', processed
			return new_sbml
		except Exception as e:
			return 'Could not update SBML file with balance results', str(e)

	return False
#----------------------------------------------------------

#Endpoint for humans to read
#Landing page
@app.route('/')
def index():
	return app.send_static_file('index.html')

#Endpoint to be used by human UI
@app.route('/balance/manual', methods=['POST'])
def manual():
	print 'Human UI'
	content = request.get_json(force=True)
	tidyList = []
	for c in content:
		if str(c[0]) is not '':
			tidyComponent = [ str(c[0]), int(c[1]), float(c[2]), str(c[3]) ]
			tidyList.append(tidyComponent)
	if len(tidyList) > 0:
		serviceFormat = { "human" : tidyList}
		print 'Hitting balancer...'
		results = list_balancer(serviceFormat)
		print '...complete'

		print 'Converting result...'
		response = results["human"]
		reaction = response[0]
		was_balanced = response[1]
		is_balanced = response[2]
		msg = response[3]
		print reaction
		reactants = []
		products = []
		for component in reaction:
			print component
			formula = component[0]
			charge = component[1]
			stoich = int(component[2])
			name = component[3]
			if stoich < 0:
				stoich = stoich * -1
				elem = str(stoich) + '.' + name
				reactants.append(elem)
			else:
				elem = str(stoich) + '.' + name
				products.append(elem)
		reactantString = ' + '.join(reactants)
		productString = ' + '.join(products)
		reactionString = reactantString + '  ->  ' + productString
		print reactionString
		return reactionString
	else:
		return 'Nothing to balance'

#JSON endpoint
@app.route('/balance/json', methods=['POST'])
def list_json():
	listOfRxns = request.get_json(force=True)
	results = list_balancer(listOfRxns)
	return json.dumps(results)

#SBML endpoint
@api.representation('application/xml')
@app.route('/balance/SBML', methods=['POST'])
def sbml():
	try:
		sbmlString = request.data
	except:
		return 'Could not access SBML data'
	response = sbml_balancer(sbmlString)
	return response

if __name__ == '__main__':
	#main()
	app.run(host='0.0.0.0',debug=False, port=8080, threaded=True)<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
from bottle import Bottle

from .context import Context

class Server(Bottle):
    """
    Serves web requests
    """

    def __init__(self,
                 context=None,
                 httpd=None,
                 route=None,
                 routes=None,
                 check=False):
        """
        Serves web requests

        :param context: global context for this process
        :type context: Context

        :param httpd: actual WSGI server

        :param route: a route to add to this instance
        :type route: Route

        :param routes: multiple routes to add to this instance
        :type routes: list of Route

        :param check: True to check configuration settings
        :type check: bool

        """
        self.context = Context() if context is None else context

        self.httpd = Bottle() if httpd is None else httpd

        self._routes = {}
        if route is not None:
            self.add_route(route)
        if routes is not None:
            self.add_routes(routes)

        if check:
            self.configure()

    def configure(self, settings={}):
        """
        Checks settings of the server

        :param settings: a dictionary with some statements for this instance
        :type settings: dict

        This function reads key ``server`` and below, and update
        the context accordingly::

            >>>shell.configure({'server': {
                   'binding': '10.4.2.5',
                   'port': 5000,
                   'debug': True,
                   }})

        This can also be written in a more compact form::

            >>>shell.configure({'server.port': 5000})

        """

        self.context.apply(settings)
        self.context.check('server.binding', '0.0.0.0')
        self.context.check('server.url', 'http://no.server', filter=True)
        self.context.check('server.port', 8080)
        self.context.check('server.debug', False)

    @property
    def routes(self):
        """
        Lists all routes

        :return: a list of routes, or []

        Example::

            >>>server.get_routes()
            ['/hello', '/world']
        """
        return sorted(self._routes.keys())

    def route(self, route):
        """
        Gets one route by path

        :return: the related route, or None
        """
        return self._routes.get(route, None)

    def add_routes(self, items):
        """
        Adds web routes

        :param routes: a list of additional routes
        :type routes: list of routes

        """
        for item in items:
            self.add_route(item)

    def add_route(self, item):
        """
        Adds one web route

        :param route: one additional route
        :type route: Route

        """
        self._routes[item.route] = item
        self.httpd.route(item.route, method="GET", callback=item.get)
        self.httpd.route(item.route, method="POST", callback=item.post)
        self.httpd.route(item.route, method="PUT", callback=item.put)
        self.httpd.route(item.route, method="DELETE", callback=item.delete)

    def run(self):
        """
        Serves requests
        """
        logging.info(u'Starting web server')

        for route in self.routes:
            logging.debug(u'- {}'.format(route))

        try:
            self.httpd.run(host=self.context.get('server.address', '0.0.0.0'),
                           port=self.context.get('server.port', 80),
                           debug=self.context.get('server.debug', False),
                           server='paste')
        except:
            pass

        logging.info(u'Web server has been stopped')
<commit_msg>streamline the handling of exceptions for web server<commit_after># -*- coding: utf-8 -*-

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
from bottle import Bottle

from .context import Context

class Server(Bottle):
    """
    Serves web requests
    """

    def __init__(self,
                 context=None,
                 httpd=None,
                 route=None,
                 routes=None,
                 check=False):
        """
        Serves web requests

        :param context: global context for this process
        :type context: Context

        :param httpd: actual WSGI server

        :param route: a route to add to this instance
        :type route: Route

        :param routes: multiple routes to add to this instance
        :type routes: list of Route

        :param check: True to check configuration settings
        :type check: bool

        """
        self.context = Context() if context is None else context

        self.httpd = Bottle() if httpd is None else httpd

        self._routes = {}
        if route is not None:
            self.add_route(route)
        if routes is not None:
            self.add_routes(routes)

        if check:
            self.configure()

    def configure(self, settings={}):
        """
        Checks settings of the server

        :param settings: a dictionary with some statements for this instance
        :type settings: dict

        This function reads key ``server`` and below, and update
        the context accordingly::

            >>>shell.configure({'server': {
                   'binding': '10.4.2.5',
                   'port': 5000,
                   'debug': True,
                   }})

        This can also be written in a more compact form::

            >>>shell.configure({'server.port': 5000})

        """

        self.context.apply(settings)
        self.context.check('server.binding', '0.0.0.0')
        self.context.check('server.url', 'http://no.server', filter=True)
        self.context.check('server.port', 8080)
        self.context.check('server.debug', False)

    @property
    def routes(self):
        """
        Lists all routes

        :return: a list of routes, or []

        Example::

            >>>server.get_routes()
            ['/hello', '/world']
        """
        return sorted(self._routes.keys())

    def route(self, route):
        """
        Gets one route by path

        :return: the related route, or None
        """
        return self._routes.get(route, None)

    def add_routes(self, items):
        """
        Adds web routes

        :param routes: a list of additional routes
        :type routes: list of routes

        """
        for item in items:
            self.add_route(item)

    def add_route(self, item):
        """
        Adds one web route

        :param route: one additional route
        :type route: Route

        """
        self._routes[item.route] = item
        self.httpd.route(item.route, method="GET", callback=item.get)
        self.httpd.route(item.route, method="POST", callback=item.post)
        self.httpd.route(item.route, method="PUT", callback=item.put)
        self.httpd.route(item.route, method="DELETE", callback=item.delete)

    def run(self):
        """
        Serves requests
        """
        logging.info(u'Starting web server')

        for route in self.routes:
            logging.debug(u'- {}'.format(route))

        self.httpd.run(host=self.context.get('server.address', '0.0.0.0'),
                       port=self.context.get('server.port', 80),
                       debug=self.context.get('server.debug', False),
                       server='paste')

        logging.info(u'Web server has been stopped')
<|endoftext|>


<commit_before># Author: William Tumeo <http://github.com/williamd1k0>.
"""
MIT License

Copyright (c) 2016 William Tumeo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

from .utils import code_block
from .plugins import *
from .data import *
from .states import State
from .maidbase import Maid
from .drinks import * # deprecated
from .commands import Command # deprecated

# Module info
__app__ = "Shiori"
__author__ = "William Tumeo <http://github.com/williamd1k0>"
__version__ = 0, 8, 0

def get_info():
    """Return maid module version info."""
    return "{0} v{1}.{2}.{3} by {4}".format(__app__, *__version__, __author__)
<commit_msg>Bump 0.9.0<commit_after># Author: William Tumeo <http://github.com/williamd1k0>.
"""
MIT License

Copyright (c) 2016 William Tumeo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

from .utils import code_block
from .plugins import *
from .data import *
from .states import State
from .maidbase import Maid
from .drinks import * # deprecated
from .commands import Command # deprecated

# Module info
__app__ = "Shiori"
__author__ = "William Tumeo <http://github.com/williamd1k0>"
__version__ = 0, 9, 0

def get_info():
    """Return maid module version info."""
    return "{0} v{1}.{2}.{3} by {4}".format(__app__, *__version__, __author__)
<|endoftext|>


<commit_before>import re
import random
from functools import total_ordering

from errors import InvalidSyntaxException,  \
                   OutOfRangeException,     \
                   TooManyComponentsException

class Dice:
    SYNTAX = re.compile(r'[+-]?(\d+)d(\d+)')

    MAX_DICE = 100
    MAX_SIDES = 1000

    def __init__(self, quantity, die, negative=False):
        self.quantity = quantity
        self.die = die
        self.negative = negative

    @classmethod
    def from_str(cls, roll_str):
        if roll_str.startswith('+') or roll_str.startswith('-'):
            sign = roll_str[0]
            roll_str = roll_str[1:]
        else:
            sign = '+'

        match = cls.SYNTAX.fullmatch(roll_str)

        if match:
            quantity = int(match.group(1))
            die = int(match.group(2))

            if quantity < 1 or quantity > cls.MAX_DICE:
                raise OutOfRangeException(
                    f'Number of dice must be between 1 and {cls.MAX_DICE}.')
            if die < 2 or die > cls.MAX_SIDES:
                raise OutOfRangeException(
                    f'Number of sides must be between 2 and {cls.MAX_SIDES}.')

            return cls(quantity, die, sign == '-')
        else:
            raise InvalidSyntaxException()

    def roll(self):
        results = []
        for i in range(self.quantity):
            results.append(random.randint(1, self.die))
        return DiceResult(self, results, self.negative)

    def __str__(self):
        sign = '-' if self.negative else ''
        return f'{sign}{self.quantity}d{self.die}'

@total_ordering
class DiceResult:
    def __init__(self, dice, results, negative):
        self.dice = dice
        self.negative = negative
        self.results = results

    def __str__(self):
        sep = ' | ' if len(self.results) > 1 else ''
        if len(self.results) > 1:
            ind_results = ', '.join(str(r) for r in self.results)
        else:
            ind_results = ''
        return f'{self.dice}: {sum(self.results)}{sep}{ind_results}'

    def __add__(self, other):
        if type(other) == int:
            return sum(self.results) + other
        else:
            return sum(self.results) + sum(other.results)

    def __radd__(self, other):
        return self.__add__(other)

    def __eq__(self, other):
        return sum(self.results) == sum(other.results)

    def __lt__(self, other):
        return sum(self.results) < sum(other.results)

class Roll:
    NORMAL = 0
    ADVANTAGE = 1
    DISADVANTAGE = 2

    SYNTAX = re.compile(r'(\d+d\d+|\d+)([+-](\d+d\d+|\d+))*')

    MAX_COMPONENTS = 25
    MAX_MODIFIER = 1000

    def __init__(self, rolls, modifiers, advantage, quantity):
        self.rolls = rolls
        self.modifiers = modifiers
        self.advantage = advantage
        self.quantity = quantity

    @classmethod
    def from_args(cls, roll_str, advantage):
        if cls.SYNTAX.fullmatch(roll_str):
            components = re.sub(r'([+-])', r' \g<1>', '+' + roll_str).split()
            rolls = []
            modifiers = []

            if len(components) > cls.MAX_COMPONENTS:
                raise TooManyComponentsException(
                    'Rolls may only have up to 25 components.')

            for comp in components:
                match = Dice.SYNTAX.match(comp)
                if match:
                    rolls.append(Dice.from_str(comp))
                else:
                    num = int(comp)
                    if num < 1 or num > cls.MAX_MODIFIER:
                        raise OutOfRangeException(
                            'Modifiers must be between 1 and '
                            f'{cls.MAX_MODIFIER}.')
                    else:
                        modifiers.append(int(comp))

            if len(rolls) == 0:
                raise InvalidSyntaxException()

            return cls(rolls, modifiers, advantage)
        else:
            raise InvalidSyntaxException()

    def roll(self):
        results = []
        for roll in self.rolls:
            results.append(roll.roll())

        if self.advantage is not self.NORMAL:
            other_results = []
            for roll in self.rolls:
                other_results.append(roll.roll())

            greater = max(results, other_results)
            lesser = min(results, other_results)

            if self.advantage is self.ADVANTAGE:
                return RollResult(greater, self.modifiers, sum(lesser))
            if self.advantage is self.DISADVANTAGE:
                return RollResult(lesser, self.modifiers, sum(greater))
        else:
            return RollResult(results, self.modifiers, None)

class RollResult:
    def __init__(self, rolls, modifiers, losing):
        self.rolls = rolls

        self.roll_total = 0
        for roll in self.rolls:
            subtotal = sum(roll.results)
            if not roll.negative:
                self.roll_total += subtotal
            else:
                self.roll_total -= subtotal

        self.modifiers = modifiers
        self.mod_total = sum(self.modifiers)
        self.total = self.roll_total + self.mod_total

        self.losing = losing

    def __str__(self):
        output = ''

        if len(self.rolls) == 1 and len(self.modifiers) == 0:
            return str(self.rolls[0])

        total = f'Total: {self.total}\n'
        roll = '\n'.join(str(r) for r in self.rolls)

        if len(self.modifiers) == 0:
            output += total + roll
        elif len(self.modifiers) == 1:
            output += f'\nModifier: {self.mod_total}'
        else:
            modifiers = ', '.join(str(m) for m in self.modifiers)
            output += f'\nModifiers: {self.mod_total} | {modifiers}'

        if self.losing is None:
            return output
        else:
            return output + f'\nOther roll: {self.losing}'

class RollCommand:
    def __init__(self, rolls):
        self.rolls = rolls

    @staticmethod
    def from_args(args):
        parts = []

        base_part = {
            'roll': None,
            'adv': False,
            'dis': False,
            'quantity': 1
        }

        for arg in args:
            if arg[0].isdigit():
                cur_part = base_part.copy()
                parts.append(cur_part)
                cur_part['roll'] = Roll.from_str(arg)
            else:
                if cur_part['roll'] is None:
                    raise InvalidSyntaxException()

                if 'advantage'.startswith(arg):
                    cur_part['adv'] = True
                elif 'disadvantage'.startswith(arg):
                    cur_part['dis'] = True
                elif arg.startswith('x') and arg[1:].isdigit():
                    cur_part['quantity'] = int(arg[1:])
                else:
                    raise InvalidSyntaxException()

        result_str = ''
        for part in parts:
            for i in range(part['quantity']):
                if len(result_str) > 0:
                    result_str += '\n\n'

                r1, r2 = part['roll'].roll(), part['roll'].roll()

                if part['adv']:
                    if r1.total >= r2.total:
                        result_str += str(r1)
                        result_str += '\nOther roll: {0}'.format(r2.total)
                    else:
                        result_str += str(r2)
                        result_str += '\nOther roll: {0}'.format(r1.total)
                elif part['dis']:
                    if r1.total <= r2.total:
                        result_str += str(r1)
                        result_str += '\nOther roll: {0}'.format(r2.total)
                    else:
                        result_str += str(r2)
                        result_str += '\nOther roll: {0}'.format(r1.total)
                else:
                    result_str += str(r1)

        return result_str

class RollCommandResult:
    def __init__(self):
        pass
<commit_msg>Fix bugs discovered in tests<commit_after>import re
import random
from functools import total_ordering

from errors import *

class Dice:
    SYNTAX = re.compile(r'[+-]?(\d+)d(\d+)')

    MAX_DICE = 100
    MAX_SIDES = 1000

    def __init__(self, quantity, die, negative=False):
        if quantity < 1 or quantity > self.MAX_DICE:
            raise OutOfRangeException(
                f'Number of dice must be between 1 and {self.MAX_DICE}.')
        if die < 2 or die > self.MAX_SIDES:
            raise OutOfRangeException(
                f'Number of sides must be between 2 and {self.MAX_SIDES}.')

        self.quantity = quantity
        self.die = die
        self.negative = negative

    @classmethod
    def from_str(cls, roll_str):
        if roll_str.startswith('+') or roll_str.startswith('-'):
            sign = roll_str[0]
            roll_str = roll_str[1:]
        else:
            sign = '+'

        match = cls.SYNTAX.fullmatch(roll_str)

        if match:
            quantity = int(match.group(1))
            die = int(match.group(2))
            return cls(quantity, die, sign == '-')
        else:
            raise InvalidSyntaxException()

    def roll(self):
        results = []
        for i in range(self.quantity):
            results.append(random.randint(1, self.die))
        return DiceResult(self, results, self.negative)

    def __str__(self):
        sign = '-' if self.negative else ''
        return f'{sign}{self.quantity}d{self.die}'

@total_ordering
class DiceResult:
    def __init__(self, dice, results, negative):
        self.dice = dice
        self.negative = negative
        self.results = results

    def __str__(self):
        sep = ' | ' if len(self.results) > 1 else ''
        if len(self.results) > 1:
            ind_results = ', '.join(str(r) for r in self.results)
        else:
            ind_results = ''
        return f'{self.dice}: {sum(self.results)}{sep}{ind_results}'

    def __add__(self, other):
        if type(other) == int:
            return sum(self.results) + other
        else:
            return sum(self.results) + sum(other.results)

    def __radd__(self, other):
        return self.__add__(other)

    def __eq__(self, other):
        return sum(self.results) == sum(other.results)

    def __lt__(self, other):
        return sum(self.results) < sum(other.results)

class Roll:
    NORMAL = 0
    ADVANTAGE = 1
    DISADVANTAGE = 2

    SYNTAX = re.compile(r'(\d+d\d+|\d+)([+-](\d+d\d+|\d+))*')

    MAX_COMPONENTS = 25
    MAX_MODIFIER = 1000

    def __init__(self, rolls, modifiers, advantage):
        components = len(rolls) + len(modifiers)
        if components < 1 or components > self.MAX_COMPONENTS:
            raise TooManyComponentsException(
                f'Rolls may only have up to {self.MAX_COMPONENTS} components.')

        for modifier in modifiers:
            if modifier < 1 or modifier > self.MAX_MODIFIER:
                raise OutOfRangeException(
                    f'Modifiers must be between 1 and {self.MAX_MODIFIER}.')

        self.rolls = rolls
        self.modifiers = modifiers
        self.advantage = advantage

    @classmethod
    def from_args(cls, roll_str, advantage):
        if cls.SYNTAX.fullmatch(roll_str):
            components = re.sub(r'([+-])', r' \g<1>', '+' + roll_str).split()
            rolls = []
            modifiers = []

            for comp in components:
                match = Dice.SYNTAX.match(comp)
                if match:
                    rolls.append(Dice.from_str(comp))
                else:
                    modifiers.append(int(comp))

            if len(rolls) == 0:
                raise InvalidSyntaxException()

            return cls(rolls, modifiers, advantage)
        else:
            raise InvalidSyntaxException()

    def roll(self):
        results = []
        for roll in self.rolls:
            results.append(roll.roll())

        if self.advantage is not self.NORMAL:
            other_results = []
            for roll in self.rolls:
                other_results.append(roll.roll())

            greater = max(results, other_results)
            lesser = min(results, other_results)

            if self.advantage is self.ADVANTAGE:
                return RollResult(greater, self.modifiers, sum(lesser))
            if self.advantage is self.DISADVANTAGE:
                return RollResult(lesser, self.modifiers, sum(greater))
        else:
            return RollResult(results, self.modifiers, None)

class RollResult:
    def __init__(self, rolls, modifiers, losing):
        self.rolls = rolls

        self.roll_total = 0
        for roll in self.rolls:
            subtotal = sum(roll.results)
            if not roll.negative:
                self.roll_total += subtotal
            else:
                self.roll_total -= subtotal

        self.modifiers = modifiers
        self.mod_total = sum(self.modifiers)
        self.total = self.roll_total + self.mod_total

        self.losing = losing

    def __str__(self):
        output = ''

        if len(self.rolls) == 1 and len(self.modifiers) == 0:
            return str(self.rolls[0])

        total = f'Total: {self.total}\n'
        roll = '\n'.join(str(r) for r in self.rolls)

        if len(self.modifiers) == 0:
            output += total + roll
        elif len(self.modifiers) == 1:
            output += f'\nModifier: {self.mod_total}'
        else:
            modifiers = ', '.join(str(m) for m in self.modifiers)
            output += f'\nModifiers: {self.mod_total} | {modifiers}'

        if self.losing is None:
            return output
        else:
            return output + f'\nOther roll: {self.losing}'

class RollCommand:
    def __init__(self, rolls):
        self.rolls = rolls

    @staticmethod
    def from_args(args):
        parts = []

        base_part = {
            'roll': None,
            'adv': False,
            'dis': False,
            'quantity': 1
        }

        for arg in args:
            if arg[0].isdigit():
                cur_part = base_part.copy()
                parts.append(cur_part)
                cur_part['roll'] = Roll.from_str(arg)
            else:
                if cur_part['roll'] is None:
                    raise InvalidSyntaxException()

                if 'advantage'.startswith(arg):
                    cur_part['adv'] = True
                elif 'disadvantage'.startswith(arg):
                    cur_part['dis'] = True
                elif arg.startswith('x') and arg[1:].isdigit():
                    cur_part['quantity'] = int(arg[1:])
                else:
                    raise InvalidSyntaxException()

        result_str = ''
        for part in parts:
            for i in range(part['quantity']):
                if len(result_str) > 0:
                    result_str += '\n\n'

                r1, r2 = part['roll'].roll(), part['roll'].roll()

                if part['adv']:
                    if r1.total >= r2.total:
                        result_str += str(r1)
                        result_str += '\nOther roll: {0}'.format(r2.total)
                    else:
                        result_str += str(r2)
                        result_str += '\nOther roll: {0}'.format(r1.total)
                elif part['dis']:
                    if r1.total <= r2.total:
                        result_str += str(r1)
                        result_str += '\nOther roll: {0}'.format(r2.total)
                    else:
                        result_str += str(r2)
                        result_str += '\nOther roll: {0}'.format(r1.total)
                else:
                    result_str += str(r1)

        return result_str

class RollCommandResult:
    def __init__(self):
        pass
<|endoftext|>


<commit_before>import pyactiveresource.formats
import time
import urllib
try:
    from hashlib import md5
except ImportError:
    from md5 import md5
try:
    import simplejson as json
except ImportError:
    import json
import re

# Partial JSON support needed only for authentication.
# Full JSON support requires a patch sent upstream to pyactiveresource.
class JSONFormat(pyactiveresource.formats.Base):
    extension = 'json'
    mime_type = 'application/json'

    @staticmethod
    def decode(resource_string):
        data = json.loads(resource_string)
        if isinstance(data, dict) and len(data) == 1:
            return data.values()[0]
        return data

class ValidationException(Exception):
    pass

class Session(object):
    api_key = None
    secret = None
    protocol = 'https'

    @classmethod
    def setup(cls, **kwargs):
        for k, v in kwargs.iteritems():
            setattr(cls, k, v)

    def __init__(self, shop_url, params=None):
        self.url = self.__prepare_url(shop_url)
        self.token = None
        self.legacy = False

        if params is None:
            return

        if not self.validate_signature(params) or \
           not int(params['timestamp']) > time.time() - 24 * 60 * 60:
            raise ValidationException('Invalid Signature: Possibly malicious login')

        if params.has_key('code'):
            # OAuth2
            self.token = self.request_token(params['code'])
        else:
            # Legacy
            self.legacy = True
            self.token = self.__computed_password(params['t'])

    def shop(self):
        Shop.current()

    @classmethod
    def create_permission_url(cls, shop_url, scope=None, redirect_url=None):
        shop_url = cls.__prepare_url(shop_url)
        if scope:
            # OAuth2
            query_params = dict(client_id=cls.api_key, scope=",".join(scope))
            if redirect_url: query_params['redirect_url'] = redirect_url
            return "%s://%s/admin/oauth/authorize?%s" % (cls.protocol, shop_url, urllib.urlencode(query_params))
        else:
            # Legacy
            return "%s://%s/admin/api/auth?api_key=%s" % (cls.protocol, shop_url, cls.api_key)

    @property
    def site(self):
        if self.legacy:
            # deprecated backwards compatiblity for setting ShopifyResource.site directly
            return "%s://%s:%s@%s/admin" % (self.protocol, self.api_key, self.token, self.url)
        else:
            return "%s://%s/admin" % (self.protocol, self.url)

    def request_token(self, code):
        from shopify.base import ShopifyResource, ShopifyConnection
        if self.token:
            return self.token
        site = "%s://%s" % (self.protocol, self.url)
        params = (self.api_key, self.secret, code)
        access_token_path = "/admin/oauth/access_token?client_id=%s&client_secret=%s&code=%s" % params
        connection = ShopifyConnection(site, None, None, ShopifyResource.timeout, JSONFormat)
        response = connection.post(access_token_path, ShopifyResource.headers)
        body = json.loads(response.body)
        if response.code == 200:
            self.token = body['access_token']
            return self.token
        else:
            raise response.response

    def __computed_password(self, t):
        return md5(self.secret + t).hexdigest()

    @staticmethod
    def __prepare_url(url):
        if url.strip() == "":
            return None
        url = re.sub("https?://", "", url)
        url = re.sub("/.*", "", url)
        if url.find(".") == -1:
            url += ".myshopify.com"
        return url

    @classmethod
    def validate_signature(cls, params):
        if "signature" not in params:
            return False

        sorted_params = ""
        signature = params['signature']

        for k in sorted(params.keys()):
            if k != "signature":
                sorted_params += k + "=" + params[k]

        return md5(cls.secret + sorted_params).hexdigest() == signature
<commit_msg>Allow manual parameter validation through Session.validate_params.<commit_after>import pyactiveresource.formats
import time
import urllib
try:
    from hashlib import md5
except ImportError:
    from md5 import md5
try:
    import simplejson as json
except ImportError:
    import json
import re

# Partial JSON support needed only for authentication.
# Full JSON support requires a patch sent upstream to pyactiveresource.
class JSONFormat(pyactiveresource.formats.Base):
    extension = 'json'
    mime_type = 'application/json'

    @staticmethod
    def decode(resource_string):
        data = json.loads(resource_string)
        if isinstance(data, dict) and len(data) == 1:
            return data.values()[0]
        return data

class ValidationException(Exception):
    pass

class Session(object):
    api_key = None
    secret = None
    protocol = 'https'

    @classmethod
    def setup(cls, **kwargs):
        for k, v in kwargs.iteritems():
            setattr(cls, k, v)

    def __init__(self, shop_url, params=None):
        self.url = self.__prepare_url(shop_url)
        self.token = None
        self.legacy = False

        if params is None:
            return

        if not self.validate_params(params):
            raise ValidationException('Invalid Signature: Possibly malicious login')

        if params.has_key('code'):
            # OAuth2
            self.token = self.request_token(params['code'])
        else:
            # Legacy
            self.legacy = True
            self.token = self.__computed_password(params['t'])

    def shop(self):
        Shop.current()

    @classmethod
    def create_permission_url(cls, shop_url, scope=None, redirect_url=None):
        shop_url = cls.__prepare_url(shop_url)
        if scope:
            # OAuth2
            query_params = dict(client_id=cls.api_key, scope=",".join(scope))
            if redirect_url: query_params['redirect_url'] = redirect_url
            return "%s://%s/admin/oauth/authorize?%s" % (cls.protocol, shop_url, urllib.urlencode(query_params))
        else:
            # Legacy
            return "%s://%s/admin/api/auth?api_key=%s" % (cls.protocol, shop_url, cls.api_key)

    @property
    def site(self):
        if self.legacy:
            # deprecated backwards compatiblity for setting ShopifyResource.site directly
            return "%s://%s:%s@%s/admin" % (self.protocol, self.api_key, self.token, self.url)
        else:
            return "%s://%s/admin" % (self.protocol, self.url)

    def request_token(self, code):
        from shopify.base import ShopifyResource, ShopifyConnection
        if self.token:
            return self.token
        site = "%s://%s" % (self.protocol, self.url)
        params = (self.api_key, self.secret, code)
        access_token_path = "/admin/oauth/access_token?client_id=%s&client_secret=%s&code=%s" % params
        connection = ShopifyConnection(site, None, None, ShopifyResource.timeout, JSONFormat)
        response = connection.post(access_token_path, ShopifyResource.headers)
        body = json.loads(response.body)
        if response.code == 200:
            self.token = body['access_token']
            return self.token
        else:
            raise response.response

    def __computed_password(self, t):
        return md5(self.secret + t).hexdigest()

    @staticmethod
    def __prepare_url(url):
        if url.strip() == "":
            return None
        url = re.sub("https?://", "", url)
        url = re.sub("/.*", "", url)
        if url.find(".") == -1:
            url += ".myshopify.com"
        return url

    @classmethod
    def validate_params(cls, params):
        # Avoid replay attacks by making sure the request
        # isn't more than a day old.
        one_day = 24 * 60 * 60
        if int(params['timestamp']) < time.time() - one_day:
            return False

        return cls.validate_signature(params)

    @classmethod
    def validate_signature(cls, params):
        if "signature" not in params:
            return False

        sorted_params = ""
        signature = params['signature']

        for k in sorted(params.keys()):
            if k != "signature":
                sorted_params += k + "=" + params[k]

        return md5(cls.secret + sorted_params).hexdigest() == signature
<|endoftext|>


<commit_before>import unittest

from uuid import UUID, uuid4
from main import *

class ShortUUIDTest(unittest.TestCase):
    def test_generation(self):
        self.assertEqual(len(uuid()), 22)
        self.assertEqual(len(uuid("http://www.example.com/")), 22)
        self.assertEqual(len(uuid("HTTP://www.example.com/")), 22)
        self.assertEqual(len(uuid("example.com")), 22)

    def test_encoding(self):
        u = UUID('{12345678-1234-5678-1234-567812345678}')
        self.assertEquals(encode(u), "VoVuUtBhZ6TvQSAYEqNdF5")

    def test_decoding(self):
        u = UUID('{12345678-1234-5678-1234-567812345678}')
        self.assertEquals(decode("VoVuUtBhZ6TvQSAYEqNdF5"), u)

    def test_alphabet(self):
        backup_alphabet = get_alphabet()

        alphabet = "01"
        set_alphabet(alphabet)
        self.assertEquals(alphabet, get_alphabet())

        set_alphabet("01010101010101")
        self.assertEquals(alphabet, get_alphabet())

        self.assertEquals(set(uuid()), set("01"))
        self.assertGreater(len(uuid()), 120)

        u = uuid4()
        self.assertEquals(u, decode(encode(u)))

        u = uuid()
        self.assertEquals(u, encode(decode(u)))

        self.assertRaises(ValueError, set_alphabet, "1")
        self.assertRaises(ValueError, set_alphabet, "1111111")

        set_alphabet(backup_alphabet)

if __name__ == '__main__':
    unittest.main()
<commit_msg>Fix test compatibility.<commit_after>import unittest

from uuid import UUID, uuid4
from main import *

class ShortUUIDTest(unittest.TestCase):
    def test_generation(self):
        self.assertEqual(len(uuid()), 22)
        self.assertEqual(len(uuid("http://www.example.com/")), 22)
        self.assertEqual(len(uuid("HTTP://www.example.com/")), 22)
        self.assertEqual(len(uuid("example.com")), 22)

    def test_encoding(self):
        u = UUID('{12345678-1234-5678-1234-567812345678}')
        self.assertEquals(encode(u), "VoVuUtBhZ6TvQSAYEqNdF5")

    def test_decoding(self):
        u = UUID('{12345678-1234-5678-1234-567812345678}')
        self.assertEquals(decode("VoVuUtBhZ6TvQSAYEqNdF5"), u)

    def test_alphabet(self):
        backup_alphabet = get_alphabet()

        alphabet = "01"
        set_alphabet(alphabet)
        self.assertEquals(alphabet, get_alphabet())

        set_alphabet("01010101010101")
        self.assertEquals(alphabet, get_alphabet())

        self.assertEquals(set(uuid()), set("01"))
        self.assertTrue(120 < len(uuid()) < 136)

        u = uuid4()
        self.assertEquals(u, decode(encode(u)))

        u = uuid()
        self.assertEquals(u, encode(decode(u)))

        self.assertRaises(ValueError, set_alphabet, "1")
        self.assertRaises(ValueError, set_alphabet, "1111111")

        set_alphabet(backup_alphabet)

if __name__ == '__main__':
    unittest.main()
<|endoftext|>


<commit_before>"""
The code in this file was ported from javascript from the transperth website.

As I progress, I will attempt to whittle it down to purely what is absolutely
required.
"""

from collections import defaultdict
from pprint import pprint

# from .. import BASE
BASE = 'https://www.transperth.wa.gov.au/'

_async_post_back_control_client_IDs = set()
_post_back_control_client_IDs = set()
_update_panel_client_IDs = [
    "dnn_ctr2061_SmartRiderTransactions_pnlModuleMessagePanel",
    "dnn_ctr2061_SmartRiderTransactions_pnlSmartRiderBalancePanel",
    "dnn_ctr2061_SmartRiderTransactions_rgTransactionsPanel",
    "dnn_ctr2061_SmartRiderTransactions_rdFromDatePanel",
    "dnn_ctr2061_SmartRiderTransactions_rdToDatePanel",
    "RadAjaxManager1SU"
]
_update_panel_has_children_as_triggers = [True, True, True, True, True, True]
_update_panel_IDs = [
    "dnn$ctr2061$SmartRiderTransactions$pnlModuleMessagePanel",
    "dnn$ctr2061$SmartRiderTransactions$pnlSmartRiderBalancePanel",
    "dnn$ctr2061$SmartRiderTransactions$rgTransactionsPanel",
    "dnn$ctr2061$SmartRiderTransactions$rdFromDatePanel",
    "dnn$ctr2061$SmartRiderTransactions$rdToDatePanel",
    "RadAjaxManager1SU"
]
_create_post_back_settings = lambda c, b, a: {
    'async': c,
    'panelID': b,
    'sourceElement': a
}

_unique_ID_to_client_ID = lambda a: '_'.join(a.split('$'))

def _matches_parent_ID_in_list(c, b):
    for a in b:
        if (c.starts_with(a + "_")):
            return True
    return False

def _find_nearest_element(document, target_id):
    while target_id:
        el_id = _unique_ID_to_client_ID(target_id)
        el = document.get_element_by_id(el_id, None)

        if el is not None:
            return el

        if '$' not in target_id:
            return None

        target_id = '$'.join(
            target_id.split('$')[:-1]
        )

    return None

def _get_post_back_settings(document, a, c):
    d = a
    b = None
    while a is not None:
        if a.attrib['id']:
            if not b and a.attrib['id'] in _async_post_back_control_client_IDs:
                b = _create_post_back_settings(
                    True,
                    'ScriptManager|' + c,
                    d
                )
            elif not b and a.attrib['id'] in _post_back_control_client_IDs:
                return _create_post_back_settings(False, None, None)
            else:
                if a.attrib['id'] in _update_panel_client_IDs:
                    e = _update_panel_client_IDs.index(a.attrib['id'])
                else:
                    e = -1

                if e != -1:
                    if (_update_panel_has_children_as_triggers[e]):
                        return _create_post_back_settings(
                            True,
                            _update_panel_IDs[e] + "|" + c,
                            d
                        )
                    else:
                        return _create_post_back_settings(
                            True,
                            'ScriptManager|' + c,
                            d
                        )

            if not b:
                if _matches_parent_ID_in_list(
                        a.attrib['id'],
                        _async_post_back_control_client_IDs):
                    b = _create_post_back_settings(
                        True,
                        'ScriptManager|' + c,
                        d
                    )
                elif _matches_parent_ID_in_list(a.attrib['id'],
                                                _post_back_control_client_IDs):
                    return _create_post_back_settings(False, None, None)

        a = a.getparent()

    if (not b):
        return _create_post_back_settings(False, None, None)
    else:
        return b

def retrieve_postback_settings(document):
    a = (
        "dnn$"
        "ctr2061$"
        "SmartRiderTransactions$"
        "rgTransactions$"
        "ctl00$"
        "ctl02$"
        "ctl00$"
        "ctl00"
    )
    print(a)

    f = _unique_ID_to_client_ID(a)
    d = document.get_element_by_id(f, None)

    if d is None:
        c = _find_nearest_element(document, a)
        if c is not None:
            return _get_post_back_settings(document, c, a)
        else:
            return _create_post_back_settings(
                False, None, None
            )
    else:
        return _get_post_back_settings(document, d, a)

def params_from_form(form):
    params = {}
    for el in form.xpath('.//input'):
        el_name = el.name
        if not el_name or el_name == "ScriptManager":
            continue

        tag = el.tag.upper()

        if tag == "INPUT":
            el_type = el.type.lower()
            if (el_type in {"text", "password", "hidden"} or
                    el_type in {"checkbox", "radio"} and el.checked):

                params[el_name] = '' if el.value is None else el.value

        elif tag == "SELECT":
            for option in el:
                if option.attrib['selected'] is True:
                    params[el_name] = option.value
            raise Exception()

        elif (tag == "TEXTAREA"):
            params[el_name] = el.value
        else:
            raise Exception(tag)

    return params

def post_back(session, document, form, code, date_from, date_to):
    params = params_from_form(form)

    _postBackSettings = retrieve_postback_settings(document)
    params["ScriptManager"] = _postBackSettings['panelID']

    # add in date range
    params.update({
        'dnn$ctr2061$SmartRiderTransactions$rdFromDate': (
            date_from.strftime('%Y-%d-%m')
        ),
        'dnn$ctr2061$SmartRiderTransactions$rdFromDate$dateInput': (
            date_from.strftime('%Y-%d-%m-00-00-00')
        ),
        'dnn$ctr2061$SmartRiderTransactions$rdToDate': (
            date_to.strftime('%Y-%d-%m')
        ),
        'dnn$ctr2061$SmartRiderTransactions$rdToDate$dateInput': (
            date_to.strftime('%Y-%d-%m-00-00-00')
        )
    })

    # add in smart rider card selection, for those special few with
    # more than one
    params['dnn$ctr2061$SmartRiderTransactions$ddlSmartCardNumber'] = code

    params.update({
        '__EVENTARGUMENT': (
            'FireCommand:'
            'dnn$ctr2061$SmartRiderTransactions$rgTransactions$ctl00;'
            'PageSize;'
            '50'
        )
    })
    # 'dnn$ctr2061$SmartRiderTransactions$rgTransactions$ctl00$ctl03$ctl01'
    # '$PageSizeComboBox': 50,
    # 'dnn_ctr2061_SmartRiderTransactions_rgTransactions_ctl00_ctl03_ctl01_'
    # 'PageSizeComboBox_ClientState': '',
    # 'dnn_ctr2061_SmartRiderTransactions_rgTransactions_ctl00_ctl03_ctl01_'
    # 'PageSizeComboBox_ClientState': (
    #     '{"logEntries": [],"value": "50","text": "50","enabled": true}'
    # )

    pprint(params)

    r = session.post(
        BASE + "TravelEasy/MySmartRider/tabid/71/Default.aspx",
        data=params,
        headers={
            "X-MicrosoftAjax": "Delta=true",
            "Cache-Control": "no-cache",
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 6.1; WOW64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/32.0.1700.107 Safari/537.36)"
            )
        }
    )

    updates = _parse_delta(r.text)

    if updates['pageRedirect']:
        raise Exception(('Not logged in', updates['pageRedirect']))

    return updates

def _parse_delta(c):
    updates = defaultdict(list)
    c = c.split('|')

    while c:
        if c[0] == '':
            break

        length, frag_type, frag_id, content = (
            c.pop(0), c.pop(0), c.pop(0), c.pop(0)
        )

        updates[frag_type].append({
            'id': frag_id,
            'content': content
        })

    return updates
<commit_msg>Some meaningful variable names for retrieve_postback_settings<commit_after>"""
The code in this file was ported from javascript from the transperth website.

As I progress, I will attempt to whittle it down to purely what is absolutely
required.
"""

from collections import defaultdict
from pprint import pprint

# from .. import BASE
BASE = 'https://www.transperth.wa.gov.au/'

_async_post_back_control_client_IDs = set()
_post_back_control_client_IDs = set()
_update_panel_client_IDs = [
    "dnn_ctr2061_SmartRiderTransactions_pnlModuleMessagePanel",
    "dnn_ctr2061_SmartRiderTransactions_pnlSmartRiderBalancePanel",
    "dnn_ctr2061_SmartRiderTransactions_rgTransactionsPanel",
    "dnn_ctr2061_SmartRiderTransactions_rdFromDatePanel",
    "dnn_ctr2061_SmartRiderTransactions_rdToDatePanel",
    "RadAjaxManager1SU"
]
_update_panel_has_children_as_triggers = [True, True, True, True, True, True]
_update_panel_IDs = [
    "dnn$ctr2061$SmartRiderTransactions$pnlModuleMessagePanel",
    "dnn$ctr2061$SmartRiderTransactions$pnlSmartRiderBalancePanel",
    "dnn$ctr2061$SmartRiderTransactions$rgTransactionsPanel",
    "dnn$ctr2061$SmartRiderTransactions$rdFromDatePanel",
    "dnn$ctr2061$SmartRiderTransactions$rdToDatePanel",
    "RadAjaxManager1SU"
]
_create_post_back_settings = lambda c, b, a: {
    'async': c,
    'panelID': b,
    'sourceElement': a
}

_unique_ID_to_client_ID = lambda a: '_'.join(a.split('$'))

def _matches_parent_ID_in_list(c, b):
    for a in b:
        if (c.starts_with(a + "_")):
            return True
    return False

def _find_nearest_element(document, target_id):
    while target_id:
        el_id = _unique_ID_to_client_ID(target_id)
        el = document.get_element_by_id(el_id, None)

        if el is not None:
            return el

        if '$' not in target_id:
            return None

        target_id = '$'.join(
            target_id.split('$')[:-1]
        )

    return None

def _get_post_back_settings(document, a, c):
    d = a
    b = None
    while a is not None:
        if a.attrib['id']:
            if not b and a.attrib['id'] in _async_post_back_control_client_IDs:
                b = _create_post_back_settings(
                    True,
                    'ScriptManager|' + c,
                    d
                )
            elif not b and a.attrib['id'] in _post_back_control_client_IDs:
                return _create_post_back_settings(False, None, None)
            else:
                if a.attrib['id'] in _update_panel_client_IDs:
                    e = _update_panel_client_IDs.index(a.attrib['id'])
                else:
                    e = -1

                if e != -1:
                    if (_update_panel_has_children_as_triggers[e]):
                        return _create_post_back_settings(
                            True,
                            _update_panel_IDs[e] + "|" + c,
                            d
                        )
                    else:
                        return _create_post_back_settings(
                            True,
                            'ScriptManager|' + c,
                            d
                        )

            if not b:
                if _matches_parent_ID_in_list(
                        a.attrib['id'],
                        _async_post_back_control_client_IDs):
                    b = _create_post_back_settings(
                        True,
                        'ScriptManager|' + c,
                        d
                    )
                elif _matches_parent_ID_in_list(a.attrib['id'],
                                                _post_back_control_client_IDs):
                    return _create_post_back_settings(False, None, None)

        a = a.getparent()

    if (not b):
        return _create_post_back_settings(False, None, None)
    else:
        return b

def retrieve_postback_settings(document, action_code):
    el_id = _unique_ID_to_client_ID(action_code)
    el = document.get_element_by_id(el_id, None)

    if el is None:
        c = _find_nearest_element(document, action_code)
        if c is not None:
            return _get_post_back_settings(document, c, action_code)
        else:
            return _create_post_back_settings(
                False, None, None
            )
    else:
        return _get_post_back_settings(document, el, action_code)

def params_from_form(form):
    params = {}
    for el in form.xpath('.//input'):
        el_name = el.name
        if not el_name or el_name == "ScriptManager":
            continue

        tag = el.tag.upper()

        if tag == "INPUT":
            el_type = el.type.lower()
            if (el_type in {"text", "password", "hidden"} or
                    el_type in {"checkbox", "radio"} and el.checked):

                params[el_name] = '' if el.value is None else el.value

        elif tag == "SELECT":
            for option in el:
                if option.attrib['selected'] is True:
                    params[el_name] = option.value
            raise Exception()

        elif (tag == "TEXTAREA"):
            params[el_name] = el.value
        else:
            raise Exception(tag)

    return params

def post_back(session, document, form, code, date_from, date_to):
    params = params_from_form(form)

    _postBackSettings = retrieve_postback_settings(document)
    params["ScriptManager"] = _postBackSettings['panelID']

    # add in date range
    params.update({
        'dnn$ctr2061$SmartRiderTransactions$rdFromDate': (
            date_from.strftime('%Y-%d-%m')
        ),
        'dnn$ctr2061$SmartRiderTransactions$rdFromDate$dateInput': (
            date_from.strftime('%Y-%d-%m-00-00-00')
        ),
        'dnn$ctr2061$SmartRiderTransactions$rdToDate': (
            date_to.strftime('%Y-%d-%m')
        ),
        'dnn$ctr2061$SmartRiderTransactions$rdToDate$dateInput': (
            date_to.strftime('%Y-%d-%m-00-00-00')
        )
    })

    # add in smart rider card selection, for those special few with
    # more than one
    params['dnn$ctr2061$SmartRiderTransactions$ddlSmartCardNumber'] = code

    params.update({
        '__EVENTARGUMENT': (
            'FireCommand:'
            'dnn$ctr2061$SmartRiderTransactions$rgTransactions$ctl00;'
            'PageSize;'
            '50'
        )
    })
    # 'dnn$ctr2061$SmartRiderTransactions$rgTransactions$ctl00$ctl03$ctl01'
    # '$PageSizeComboBox': 50,
    # 'dnn_ctr2061_SmartRiderTransactions_rgTransactions_ctl00_ctl03_ctl01_'
    # 'PageSizeComboBox_ClientState': '',
    # 'dnn_ctr2061_SmartRiderTransactions_rgTransactions_ctl00_ctl03_ctl01_'
    # 'PageSizeComboBox_ClientState': (
    #     '{"logEntries": [],"value": "50","text": "50","enabled": true}'
    # )

    pprint(params)

    r = session.post(
        BASE + "TravelEasy/MySmartRider/tabid/71/Default.aspx",
        data=params,
        headers={
            "X-MicrosoftAjax": "Delta=true",
            "Cache-Control": "no-cache",
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 6.1; WOW64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/32.0.1700.107 Safari/537.36)"
            )
        }
    )

    updates = _parse_delta(r.text)

    if updates['pageRedirect']:
        raise Exception(('Not logged in', updates['pageRedirect']))

    return updates

def _parse_delta(c):
    updates = defaultdict(list)
    c = c.split('|')

    while c:
        if c[0] == '':
            break

        length, frag_type, frag_id, content = (
            c.pop(0), c.pop(0), c.pop(0), c.pop(0)
        )

        updates[frag_type].append({
            'id': frag_id,
            'content': content
        })

    return updates
<|endoftext|>


<commit_before>'''
Created on Dec 15, 2013
Overhauled  on Jan 22, 2015

@author: DavidGrey
'''

from random import choice
from ascii import *
from msvcrt import getwch
from states import game_states

clear = "\n" * 20

def get_values(state):
    """Takes a dictionary game state as inputs,
    extracts and returns the values of that dict in a tuple"""
    keys = ['player_ammo', 'player_block', 'player_prev', 'comp_ammo', 'comp_block', 'comp_prev']
    return tuple([state[key] for key in keys])

def get_move(state):
    """Takes a game state as input,
    returns a move based on locked bool values
    and states weights"""
    entry = game_states[get_values(state)]
    winners = []

    for move in entry:
        next = entry[move]
        if next == 'Y':
            return move
        elif next == 'N':
            continue
        winners += [move]*next
    else:
        return choice(winners)

def run_match(player_move, comp_move, state):
    """Takes 2 moves and a game state as input,
    updates the curr_state variable,
    returns the game_states entry for the modified state"""
    player_vul = False
    comp_vul = False
    state['player_prev'] = player_move
    state['comp_prev'] = comp_move

    #Update game variables
    if player_move == 'd':
        state['player_ammo'] += 1
        player_vul = True
    if comp_move == 'd':
        state['comp_ammo'] += 1
        comp_vul = True
    if player_move == 'a':
        if comp_vul:
            return 'N'
        state['player_ammo'] -= 1
    if comp_move == 'a':
        if player_vul:
            return 'Y'
        state['comp_ammo'] -= 1

    return game_states[get_values(state)]

def main(round = 0):
    print clear*2
    curr_state = {'player_ammo':1, 'player_block':True, 'player_prev':'d',
                   'comp_ammo':1, 'comp_block':True, 'comp_prev':'d'}
    lock = False
    player_blocks = 0
    comp_blocks = 0
    ascii = {'a':gun,
             's': shield,
             'd':reload,
             'Y':loss,
             'N':win}
    #Main loop
    while True:
        round += 1
        print 'Round '+str(round)+':\n'+' Your Bullets| ' + '*'*curr_state['player_ammo']+'\n My Bullets  | ' + '*'*curr_state['comp_ammo']+'\n'
        values = get_values(curr_state)
        #First move isn't pulled from game states
        if round > 1:
            #If guaranteed a win, the AI locks itself into firing mode
            if curr_state['comp_ammo'] > (curr_state['player_ammo'] + (3-player_blocks)):
                comp_move = 'a'
                lock = True
            if not lock:
                comp_move = get_move(curr_state)
            else:
                comp_move = 'a'
        else:
            comp_move = random.choice(['a','s','d'])

        #Player selects move
        while True:
            print ":"
            player_move = getwch()# raw_input(":") #getwch()
            #Confirm player move is valid
            if player_move in ['a','s','d']:
                if player_move == 'a' and not curr_state['player_ammo']:
                    print 'You can\'t fire'
                    continue

                elif player_move == 's' and not curr_state['player_block']:
                    print 'You can\'t block'
                    continue

                elif player_move == 'd' and curr_state['player_ammo'] == 6:
                    print 'You can\'t reload'
                    continue
                break
            else:
                print clear+"Invalid input\n"+'Round '+str(round)+':\n'

        #Update block variables
        if player_move == 's':
            player_blocks += 1
            if player_blocks == 3:
                curr_state['player_block'] = False
        else:
            player_blocks = 0
            curr_state['player_block'] = True

        if comp_move == 's':
            comp_blocks += 1
            if comp_blocks == 3:
                curr_state['comp_block'] = False
        else:
            comp_blocks = 0
            curr_state['comp_block'] = True

        #Print match
        print clear + ascii[player_move] +'\n'*2 + ascii[comp_move] + '\n'

        result = run_match(player_move, comp_move, curr_state)

        #<Learning Code>
        if player_move == 'a':
            if game_states[values]['s'] not in [ 'N', 'Y']:
                game_states[values]['s'] += 1
            elif game_states[values]['a'] not in [ 'N', 'Y']:
                game_states[values]['a'] += 1

        elif player_move == 's':
            if game_states[values]['d'] not in [ 'N', 'Y']:
                game_states[values]['d'] += 1
            elif game_states[values]['s'] not in [ 'N', 'Y']:
                game_states[values]['s'] += 1

        elif player_move == 'd':
            if game_states[values]['a'] not in [ 'N', 'Y']:
                game_states[values]['a'] += 1
            elif game_states[values]['d'] not in [ 'N', 'Y']:
                game_states[values]['d'] += 1
        #<\Learning Code>

        if result in ['Y', 'N']:
            with open("states.py", "w") as states:
                states.write('game_states='+str(game_states))
            return ascii[result]
            input("stop")

if __name__ == '__main__':
    print main()
    while True:
        print "Press any key to play again"
        getwch()
        print main()
<commit_msg>Update main.py<commit_after>'''
Created on Dec 15, 2013
Overhauled  on Jan 22, 2015

@author: DavidGrey
'''

from random import choice
from ascii import *
from msvcrt import getwch
from states import game_states

clear = "\n" * 20

def get_values(state):
    """Takes a dictionary game state as inputs,
    extracts and returns the values of that dict in a tuple"""
    keys = ['player_ammo', 'player_block', 'player_prev', 'comp_ammo', 'comp_block', 'comp_prev']
    return tuple([state[key] for key in keys])

def get_move(state):
    """Takes a game state as input,
    returns a move based on locked bool values
    and states weights"""
    entry = game_states[get_values(state)]
    winners = []

    for move in entry:
        next = entry[move]
        if next == 'Y':
            return move
        elif next == 'N':
            continue
        winners += [move]*next
    else:
        return choice(winners)

def run_match(player_move, comp_move, state):
    """Takes 2 moves and a game state as input,
    updates the curr_state variable,
    returns the game_states entry for the modified state"""
    player_vul = False
    comp_vul = False
    state['player_prev'] = player_move
    state['comp_prev'] = comp_move

    #Update game variables
    if player_move == 'd':
        state['player_ammo'] += 1
        player_vul = True
    if comp_move == 'd':
        state['comp_ammo'] += 1
        comp_vul = True
    if player_move == 'a':
        if comp_vul:
            return 'N'
        state['player_ammo'] -= 1
    if comp_move == 'a':
        if player_vul:
            return 'Y'
        state['comp_ammo'] -= 1

    return game_states[get_values(state)]

def main(round = 0):
    print clear*2
    curr_state = {'player_ammo':1, 'player_block':True, 'player_prev':'d',
                   'comp_ammo':1, 'comp_block':True, 'comp_prev':'d'}
    lock = False
    player_blocks = 0
    comp_blocks = 0
    ascii = {'a':gun,
             's': shield,
             'd':reload,
             'Y':loss,
             'N':win}
    #Main loop
    while True:
        round += 1
        print 'Round '+str(round)+':\n'+' Your Bullets| ' + '*'*curr_state['player_ammo']+'\n My Bullets  | ' + '*'*curr_state['comp_ammo']+'\n'
        values = get_values(curr_state)
        #First move isn't pulled from game states
        if round > 1:
            #If guaranteed a win, the AI locks itself into firing mode
            if curr_state['comp_ammo'] > (curr_state['player_ammo'] + (3-player_blocks)):
                comp_move = 'a'
                lock = True
            if not lock:
                comp_move = get_move(curr_state)
            else:
                comp_move = 'a'
        else:
            comp_move = choice(['a','s','d'])

        #Player selects move
        while True:
            print ":"
            player_move = getwch()# raw_input(":") #getwch()
            #Confirm player move is valid
            if player_move in ['a','s','d']:
                if player_move == 'a' and not curr_state['player_ammo']:
                    print 'You can\'t fire'
                    continue

                elif player_move == 's' and not curr_state['player_block']:
                    print 'You can\'t block'
                    continue

                elif player_move == 'd' and curr_state['player_ammo'] == 6:
                    print 'You can\'t reload'
                    continue
                break
            else:
                print clear+"Invalid input\n"+'Round '+str(round)+':\n'

        #Update block variables
        if player_move == 's':
            player_blocks += 1
            if player_blocks == 3:
                curr_state['player_block'] = False
        else:
            player_blocks = 0
            curr_state['player_block'] = True

        if comp_move == 's':
            comp_blocks += 1
            if comp_blocks == 3:
                curr_state['comp_block'] = False
        else:
            comp_blocks = 0
            curr_state['comp_block'] = True

        #Print match
        print clear + ascii[player_move] +'\n'*2 + ascii[comp_move] + '\n'

        result = run_match(player_move, comp_move, curr_state)

        #<Learning Code>
        if player_move == 'a':
            if game_states[values]['s'] not in [ 'N', 'Y']:
                game_states[values]['s'] += 1
            elif game_states[values]['a'] not in [ 'N', 'Y']:
                game_states[values]['a'] += 1

        elif player_move == 's':
            if game_states[values]['d'] not in [ 'N', 'Y']:
                game_states[values]['d'] += 1
            elif game_states[values]['s'] not in [ 'N', 'Y']:
                game_states[values]['s'] += 1

        elif player_move == 'd':
            if game_states[values]['a'] not in [ 'N', 'Y']:
                game_states[values]['a'] += 1
            elif game_states[values]['d'] not in [ 'N', 'Y']:
                game_states[values]['d'] += 1
        #<\Learning Code>

        if result in ['Y', 'N']:
            with open("states.py", "w") as states:
                states.write('game_states='+str(game_states))
            return ascii[result]
            input("stop")

if __name__ == '__main__':
    print main()
    while True:
        print "Press any key to play again"
        getwch()
        print main()
<|endoftext|>