<commit_before>import unittest
import json
STC = 25

class module(object):
    """base module class which specific modules inherit"""
    STC = 25
    #PV module nameplate DC rating     0.80 - 1.05
    #SAM = 1
    #PVWatts = .95
    nameplate = .95
    Vrated = 600
    #nameplate = 1
    #beta Voc %/Tempunit
    #gamma Pmax %/Tempunit
    def __init__(self):
        pass
    def output(self,Insolation):
        m = self.__class__
        return Insolation*m.A*m.Eff*m.nameplate
    def Vmax(self,ashraeMin):
        m = self.__class__
        return m.Voc + (ashraeMin-m.STC)*m.TkVoc
    def Vdc(self):
        m = self.__class__
        return m.Vmpp

    def Vmin(self,ashrae2p,Tadd = 30):
        #Tadd
        #Roof mount =30
        #Ground mount = 25
        #Pole mount = 20
        m = self.__class__
        return m.Vmpp + (Tadd+ashrae2p-m.STC)*m.TkVoc
        #tkVmp or tkVoc?
        #return m.Vmpp + (Tadd+ashrae2p-m.STC)*m.TkVmp

class moduleJ(object):
    """base module class which specific modules inherit"""
    #PV module nameplate DC rating     0.80 - 1.05
    #SAM = 1
    #PVWatts = .95
    nameplate = .95
    Vrated = 600
    #nameplate = 1
    #beta Voc %/Tempunit
    #gamma Pmax %/Tempunit
    def __init__(self,model):
        self.properties = None
        panels = json.loads(open('sp.json').read())
        for i in panels:
            if i['panel']==model:
                self.properties = i
                break
        if self.properties == None:
            raise Exception("Panel not found")
        self.make = model.split(':')[0].rstrip()
        self.model = model.split(':')[1].strip()
        self.Vmpp = self.properties['v_mp_ref']
        self.Impp = self.properties['i_mp_ref']
        self.Pmax = self.Vmpp*self.Impp
        self.Isc =  self.properties['i_sc_ref']
        self.Voc = self.properties['v_oc_ref']
        #self.beta = self.properties['beta_oc']
        #self.gamma = self.properties['gamma_r']
        #Voc V/C
        self.TkVoc = self.properties['beta_oc']
        #Pmp W/C
        self.TkPmp = self.properties['gamma_r']*self.Pmax/100
        self.gamma = self.properties['gamma_r']
        self.TkVmp = self.properties['gamma_r']*self.Vmpp/100
        self.A = self.properties['a_c']
        self.Eff = self.Pmax/self.A/1000
        self.nameplate = 1.0

    def output(self,Insolation):
        return Insolation * self.A * self.Eff * self.nameplate
    def Vmax(self,ashraeMin):
        return self.Voc + (ashraeMin-STC) * self.TkVoc
    def Vdc(self):
        return self.Vmpp

    def Vmin(self,ashrae2p,Tadd = 30):
        #Tadd
        #Roof mount =30
        #Ground mount = 25
        #Pole mount = 20
        return self.Vmpp + (Tadd+ashrae2p-STC) * self.TkVmp

#this needs rewritten
class pvArray(module):
    """structure to aggregate panels into an array)"""
    def __init__(self,pname, series, parallel = 1):
        self.panel = pname
        self.series = series
        self.parallel = parallel
        self.Pmax = pname.Pmax*series*parallel

    def Vdc(self):
        return self.panel.Vdc() * self.series
    def Vmax(self,ashraeMin):
        return self.panel.Vmax(ashraeMin) * self.series
    def Vmin(self,ashrae2p, Tadd = 30):
        return  self.panel.Vmin(ashrae2p, Tadd)* self.series
    def output(self, Insolation):
        return self.panel.output(Insolation)*self.series*self.parallel

class mage285(module):
    make = "Mage"
    model = ""
    Pmax = 285
    Vmpp = 35.59
    Impp = 8.01
    Isc = 8.45
    Voc = 44.76
    #Uoc %/K
    beta = -0.32
    #Pmax %/K
    gamma = -0.43
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.973 * .989
    Eff = .146
    nameplate = 1.0

class mage250(module):
    make = "Mage"
    model = "250/6 MNS"
    Pmax = 250
    Vrated = 600
    Vmpp = 30.55
    Impp = 8.21
    Isc = 8.70
    Voc = 37.70
    #Uoc %/K
    beta = -0.32
    #Pmax %/K
    gamma = -0.43
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.644 * .992
    Eff = .1533
    nameplate = 1.0

class mage250ml(module):
    make = "Mage"
    model = "250/6 ML"
    Pmax = 250
    Vrated = 600
    Vmpp = 30.90
    Impp = 8.09
    Isc = 8.79
    Voc = 37.57
    #Uoc %/K
    beta = -0.33
    #Pmax %/K
    gamma = -0.43
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.655 * .989
    Eff = .154
    nameplate = 1.0

class mage240(module):
    make = "Mage"
    Pmax = 240
    Vmpp = 29.48
    Impp = 8.14
    Isc = 8.51
    Voc = 37.48
    #Uoc %/K
    beta = -0.297
    #Pmax %/K
    gamma = -0.438
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.650 * .991
    Eff = .146

class mage235(module):
    make = "Mage"
    model = ""
    Pmax = 235
    Vmpp = 29.69
    Impp = 7.92
    Isc = 8.55
    Voc = 37.03
    #Uoc %/K
    beta = -0.32
    #Pmax %/K
    gamma = -0.43
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.655 * .989
    Eff = .144

class motech245(module):
    make = "Motech"
    model = "245"
    Pmax = 245
    Vmpp = 29.9
    Impp = 8.2
    Isc = 8.7
    Voc = 37.4
    #Voc %/C
    beta = -0.34
    #Pmax %/C
    gamma = -0.46
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.650 * .992
    Eff = Pmax/A/1000

class sinodeu120(module):
    Pmax = 120
    Vmpp = 17
    Impp = 7.06
    Isc = 7.76
    Voc = 21.2
    beta = -0.37 #%/K
    gamma = -0.52 #%/K
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.480*670
    Eff = Pmax/A/1000

class suntech285(module):
    make = "SUNTECH"
    model = "STP285-VRM-1.5"
    Pmax = 285
    Vrated = 1000
    Vmpp = 35.4
    Impp = 8.06
    Isc = 8.37
    Voc = 44.9
    #Uoc %/K
    beta = -0.33
    #Pmax %/K
    gamma = -0.44
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.956* 1.066
    Eff = .147
    nameplate = 1.0

class rec300(module):
    make = "REC"
    model = "REC300PE72"
    Pmax = 300
    Vrated = 1000
    Vmpp = 36.1
    Impp = 8.33
    Isc = 8.86
    Voc = 44.9
    #Uoc %/K
    beta = -0.28
    #Pmax %/K
    gamma = -0.41
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.9812 * .9906
    Eff = .153
    nameplate = 1.0

class generic170(module):
    make = ""
    model = ""
    Pmax = 170
    Vmpp = 34.8
    Impp =4.9
    Isc = 5.7
    Voc = 43.2
    TkVoc = -0.1728
    TkVmp = -0.1796
    Eff = .15
    A = Pmax/Eff/1000

class generic180(module):
    make = ""
    model = ""
    Pmax = 180
    Vmpp = 25.9
    Impp = 6.95
    Isc = 7.78
    Voc = 32.6
    beta = -0.34 #%/K
    gamma = -0.47 #%/K
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = -0.49 * Vmpp/100
    Eff = .15
    A = Pmax/Eff/1000

class astroenergy290(module):
    make = ""
    model = ""
    Pmax = 290
    Vmpp = 35.68
    Impp = 8.15
    Isc =  8.94
    Voc = 44.90
    beta = -0.332 #%/K
    gamma = -0.445 #%/K
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = -0.451 * Vmpp/100
    Eff = .149
    A = Pmax/Eff/1000

class asp240(module):
    Pmax = 240
    Vmpp = 30.54
    Impp = 7.87
    Isc = 8.48
    Voc = 37.26
    beta = -0.335 #%/C
    gamma = -0.40 #%/C
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = -0.40 * Vmpp/100
    Eff = .1482
    A = Pmax/Eff/1000

class asp390(module):
    make = ""
    model = ""
    Pmax = 390
    Vmpp = 49.38
    Impp = 7.92
    Isc = 8.42
    Voc = 59.62
    beta = -0.40 #%/C
    gamma = -0.49 #%/C
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = -0.49 * Vmpp/100
    Eff = .152
    A = Pmax/Eff/1000

class asw270p(module):
    make = ""
    model = ""
    Pmax = 270
    Vmpp = 35.2
    Impp = 7.670
    Isc = 8.44
    Voc = 44.4
    #Voc %/C
    beta = -0.33 #%/K
    #Pmax %/K
    gamma = -0.46 #%/K
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = gamma * Vmpp/100
    A = 1.954*.990
    Eff = Pmax/A/1000

def manufacturers():
    a =  [i['panel'].split(":")[0] for i in json.loads(open('sp.json').read()) ]
    a.sort()
    b = [i for i in set(a)]
    b.sort()
    return b

def models(manufacturer = None):
    """returns list of available panel models"""
    #return json.loads(open('si.json').read())
    if manufacturer ==None:
        return [i['panel'] for i in json.loads(open('sp.json').read()) ]
    else:
        a = []
        for i in json.loads(open('sp.json').read()):
            if i['panel'].find(manufacturer) != -1:
                a.append(i['panel'])
        return a

class testModules(unittest.TestCase):
    """Unit Tests"""
    def setUp(self):
        self.mage250o = mage250().output(950)

    def testMageOutput(self):
        self.assertAlmostEqual(237.509, self.mage250o,3)

if __name__=="__main__":
    #p = generic180()
    series = 14
    #p = sinodeu120()
    #p = motech245()
    #p = astroenergy290()
    #p = asp390()
    p = moduleJ('Mage Solar : Powertec Plus 245-6 MNBS')
    print ":%s:" %p.make
    print ":%s:" %p.model
    p = mage250()
    print p.Eff
    print p

    print "Vmax:", p.Vmax(-13)*series
    print "Vmin:",p.Vmin(31,25) * series
    print "Vmin 10%:",p.Vmin(31,25) * series*.90

    #print p.output(950)
    #a = pvArray(motech245(), 14,2)
    #a = pvArray(motech245(), 14,2)
    #print a.Vmax(-20)
    #print a.Vmin(33)
    #print a.output(950)
    suite = unittest.TestLoader().loadTestsFromTestCase(testModules)
    unittest.TextTestRunner(verbosity=2).run(suite)
<commit_msg>path indepenance<commit_after>import unittest
import json
STC = 25
import os
SPATH = os.path.dirname(os.path.abspath(__file__))

class module(object):
    """base module class which specific modules inherit"""
    STC = 25
    #PV module nameplate DC rating     0.80 - 1.05
    #SAM = 1
    #PVWatts = .95
    nameplate = .95
    Vrated = 600
    #nameplate = 1
    #beta Voc %/Tempunit
    #gamma Pmax %/Tempunit
    def __init__(self):
        pass
    def output(self,Insolation):
        m = self.__class__
        return Insolation*m.A*m.Eff*m.nameplate
    def Vmax(self,ashraeMin):
        m = self.__class__
        return m.Voc + (ashraeMin-m.STC)*m.TkVoc
    def Vdc(self):
        m = self.__class__
        return m.Vmpp

    def Vmin(self,ashrae2p,Tadd = 30):
        #Tadd
        #Roof mount =30
        #Ground mount = 25
        #Pole mount = 20
        m = self.__class__
        return m.Vmpp + (Tadd+ashrae2p-m.STC)*m.TkVoc
        #tkVmp or tkVoc?
        #return m.Vmpp + (Tadd+ashrae2p-m.STC)*m.TkVmp

class moduleJ(object):
    """base module class which specific modules inherit"""
    #PV module nameplate DC rating     0.80 - 1.05
    #SAM = 1
    #PVWatts = .95
    nameplate = .95
    Vrated = 600
    #nameplate = 1
    #beta Voc %/Tempunit
    #gamma Pmax %/Tempunit
    def __init__(self,model):
        self.properties = None
        panels = json.loads(open(SPATH + '/sp.json').read())
        for i in panels:
            if i['panel']==model:
                self.properties = i
                break
        if self.properties == None:
            raise Exception("Panel not found")
        self.make = model.split(':')[0].rstrip()
        self.model = model.split(':')[1].strip()
        self.Vmpp = self.properties['v_mp_ref']
        self.Impp = self.properties['i_mp_ref']
        self.Pmax = self.Vmpp*self.Impp
        self.Isc =  self.properties['i_sc_ref']
        self.Voc = self.properties['v_oc_ref']
        #self.beta = self.properties['beta_oc']
        #self.gamma = self.properties['gamma_r']
        #Voc V/C
        self.TkVoc = self.properties['beta_oc']
        #Pmp W/C
        self.TkPmp = self.properties['gamma_r']*self.Pmax/100
        self.gamma = self.properties['gamma_r']
        self.TkVmp = self.properties['gamma_r']*self.Vmpp/100
        self.A = self.properties['a_c']
        self.Eff = self.Pmax/self.A/1000
        self.nameplate = 1.0

    def output(self,Insolation):
        return Insolation * self.A * self.Eff * self.nameplate
    def Vmax(self,ashraeMin):
        return self.Voc + (ashraeMin-STC) * self.TkVoc
    def Vdc(self):
        return self.Vmpp

    def Vmin(self,ashrae2p,Tadd = 30):
        #Tadd
        #Roof mount =30
        #Ground mount = 25
        #Pole mount = 20
        return self.Vmpp + (Tadd+ashrae2p-STC) * self.TkVmp

#this needs rewritten
class pvArray(module):
    """structure to aggregate panels into an array)"""
    def __init__(self,pname, series, parallel = 1):
        self.panel = pname
        self.series = series
        self.parallel = parallel
        self.Pmax = pname.Pmax*series*parallel

    def Vdc(self):
        return self.panel.Vdc() * self.series
    def Vmax(self,ashraeMin):
        return self.panel.Vmax(ashraeMin) * self.series
    def Vmin(self,ashrae2p, Tadd = 30):
        return  self.panel.Vmin(ashrae2p, Tadd)* self.series
    def output(self, Insolation):
        return self.panel.output(Insolation)*self.series*self.parallel

class mage285(module):
    make = "Mage"
    model = ""
    Pmax = 285
    Vmpp = 35.59
    Impp = 8.01
    Isc = 8.45
    Voc = 44.76
    #Uoc %/K
    beta = -0.32
    #Pmax %/K
    gamma = -0.43
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.973 * .989
    Eff = .146
    nameplate = 1.0

class mage250(module):
    make = "Mage"
    model = "250/6 MNS"
    Pmax = 250
    Vrated = 600
    Vmpp = 30.55
    Impp = 8.21
    Isc = 8.70
    Voc = 37.70
    #Uoc %/K
    beta = -0.32
    #Pmax %/K
    gamma = -0.43
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.644 * .992
    Eff = .1533
    nameplate = 1.0

class mage250ml(module):
    make = "Mage"
    model = "250/6 ML"
    Pmax = 250
    Vrated = 600
    Vmpp = 30.90
    Impp = 8.09
    Isc = 8.79
    Voc = 37.57
    #Uoc %/K
    beta = -0.33
    #Pmax %/K
    gamma = -0.43
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.655 * .989
    Eff = .154
    nameplate = 1.0

class mage240(module):
    make = "Mage"
    Pmax = 240
    Vmpp = 29.48
    Impp = 8.14
    Isc = 8.51
    Voc = 37.48
    #Uoc %/K
    beta = -0.297
    #Pmax %/K
    gamma = -0.438
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.650 * .991
    Eff = .146

class mage235(module):
    make = "Mage"
    model = ""
    Pmax = 235
    Vmpp = 29.69
    Impp = 7.92
    Isc = 8.55
    Voc = 37.03
    #Uoc %/K
    beta = -0.32
    #Pmax %/K
    gamma = -0.43
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.655 * .989
    Eff = .144

class motech245(module):
    make = "Motech"
    model = "245"
    Pmax = 245
    Vmpp = 29.9
    Impp = 8.2
    Isc = 8.7
    Voc = 37.4
    #Voc %/C
    beta = -0.34
    #Pmax %/C
    gamma = -0.46
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.650 * .992
    Eff = Pmax/A/1000

class sinodeu120(module):
    Pmax = 120
    Vmpp = 17
    Impp = 7.06
    Isc = 7.76
    Voc = 21.2
    beta = -0.37 #%/K
    gamma = -0.52 #%/K
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.480*670
    Eff = Pmax/A/1000

class suntech285(module):
    make = "SUNTECH"
    model = "STP285-VRM-1.5"
    Pmax = 285
    Vrated = 1000
    Vmpp = 35.4
    Impp = 8.06
    Isc = 8.37
    Voc = 44.9
    #Uoc %/K
    beta = -0.33
    #Pmax %/K
    gamma = -0.44
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.956* 1.066
    Eff = .147
    nameplate = 1.0

class rec300(module):
    make = "REC"
    model = "REC300PE72"
    Pmax = 300
    Vrated = 1000
    Vmpp = 36.1
    Impp = 8.33
    Isc = 8.86
    Voc = 44.9
    #Uoc %/K
    beta = -0.28
    #Pmax %/K
    gamma = -0.41
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    A = 1.9812 * .9906
    Eff = .153
    nameplate = 1.0

class generic170(module):
    make = ""
    model = ""
    Pmax = 170
    Vmpp = 34.8
    Impp =4.9
    Isc = 5.7
    Voc = 43.2
    TkVoc = -0.1728
    TkVmp = -0.1796
    Eff = .15
    A = Pmax/Eff/1000

class generic180(module):
    make = ""
    model = ""
    Pmax = 180
    Vmpp = 25.9
    Impp = 6.95
    Isc = 7.78
    Voc = 32.6
    beta = -0.34 #%/K
    gamma = -0.47 #%/K
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = -0.49 * Vmpp/100
    Eff = .15
    A = Pmax/Eff/1000

class astroenergy290(module):
    make = ""
    model = ""
    Pmax = 290
    Vmpp = 35.68
    Impp = 8.15
    Isc =  8.94
    Voc = 44.90
    beta = -0.332 #%/K
    gamma = -0.445 #%/K
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = -0.451 * Vmpp/100
    Eff = .149
    A = Pmax/Eff/1000

class asp240(module):
    Pmax = 240
    Vmpp = 30.54
    Impp = 7.87
    Isc = 8.48
    Voc = 37.26
    beta = -0.335 #%/C
    gamma = -0.40 #%/C
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = -0.40 * Vmpp/100
    Eff = .1482
    A = Pmax/Eff/1000

class asp390(module):
    make = ""
    model = ""
    Pmax = 390
    Vmpp = 49.38
    Impp = 7.92
    Isc = 8.42
    Voc = 59.62
    beta = -0.40 #%/C
    gamma = -0.49 #%/C
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = -0.49 * Vmpp/100
    Eff = .152
    A = Pmax/Eff/1000

class asw270p(module):
    make = ""
    model = ""
    Pmax = 270
    Vmpp = 35.2
    Impp = 7.670
    Isc = 8.44
    Voc = 44.4
    #Voc %/C
    beta = -0.33 #%/K
    #Pmax %/K
    gamma = -0.46 #%/K
    TkVoc = beta * Voc /100
    TkVmp = gamma * Vmpp/100
    TkPmp = gamma * Vmpp/100
    A = 1.954*.990
    Eff = Pmax/A/1000

def manufacturers():
    a =  [i['panel'].split(":")[0] for i in json.loads(open('sp.json').read()) ]
    a.sort()
    b = [i for i in set(a)]
    b.sort()
    return b

def models(manufacturer = None):
    """returns list of available panel models"""
    #return json.loads(open('si.json').read())
    if manufacturer ==None:
        return [i['panel'] for i in json.loads(open('sp.json').read()) ]
    else:
        a = []
        for i in json.loads(open('sp.json').read()):
            if i['panel'].find(manufacturer) != -1:
                a.append(i['panel'])
        return a

class testModules(unittest.TestCase):
    """Unit Tests"""
    def setUp(self):
        self.mage250o = mage250().output(950)

    def testMageOutput(self):
        self.assertAlmostEqual(237.509, self.mage250o,3)

if __name__=="__main__":
    #p = generic180()
    series = 14
    #p = sinodeu120()
    #p = motech245()
    #p = astroenergy290()
    #p = asp390()
    p = moduleJ('Mage Solar : Powertec Plus 245-6 MNBS')
    print ":%s:" %p.make
    print ":%s:" %p.model
    p = mage250()
    print p.Eff
    print p

    print "Vmax:", p.Vmax(-13)*series
    print "Vmin:",p.Vmin(31,25) * series
    print "Vmin 10%:",p.Vmin(31,25) * series*.90

    #print p.output(950)
    #a = pvArray(motech245(), 14,2)
    #a = pvArray(motech245(), 14,2)
    #print a.Vmax(-20)
    #print a.Vmin(33)
    #print a.output(950)
    suite = unittest.TestLoader().loadTestsFromTestCase(testModules)
    unittest.TextTestRunner(verbosity=2).run(suite)
<|endoftext|>


<commit_before>#!/usr/bin/python
#
# Starter.py
# Author: Zex <top_zlynch@yahoo.com>
#

import time

print "Content-Type: text/html\n\n"
print "<!DOCTYPE html>"
print ""

print ""
print "<title>Starter</title>"
print "<link href=\"/css/basic.css\" rel=\"stylesheet\" type=\"text/css\">"
print "<meta charset=\"UTF-8\">"
print ""

print ""
print "
"
print ""
print "
"

print "
"
print "<form action=\"hello_baby.py\" method=get>"
print "Action: "
print "<input type=text name=fr_name/>"
print "<input type=\"submit\" value=\"Do IT!\"/>
"
print "</form>"
print "
"

print "
"
print "<form action=\"upload_file.py\" method=post enctype=\"multipart/form-data\">"
print "Select file: "
print "<input type=file name=file_name/>"
print "<input type=\"submit\" value=\"Upload!\"/>
"
print "</form>"
print "
"

print "
"

print "
"
print ""
icecreams = [ "Milk", "Chocolate", "Strawberry", "Coconut", "Peanut" ]

print "<form action=\"icecream_box.py\" method=post>"
print "Icecream:
"

for i in icecreams:
    print "<input type=checkbox name=\"", i,"\" value=\"1\"/> ", i, "
"

print "<input type=\"submit\" value=\"Done!\"/>
"
print "</form>"
print "	"
print ""

flowers = [ "Almond Blossom", "Balsam", "Anther", "Camellia", "Azalea" ]

print "<form action=\"flower_man.py\" method=post>"
print "Flower:
"

for f in flowers:
    print "<input type=radio name=flower value=\"", f,"\"/> ", f,"
"

print "<input type=\"submit\" value=\"Done!\"/>
"
print "</form>"
print "	"
print "
"
print "
"

options = [ "Go", "Walk Away", "Close", "Laught" ]
print "
"
print "<form action=\"motion_trigger.py\" method=post>"
print "Motions:
"
print "<select name=option>"

for o in options:
    print "<option>" + o

print "</select>"
print "<input type=\"submit\" value=\"Done!\"/>
"
print "</form>"
print "
"

print "
"
print "Current time: "
print "<input id=\"cur_time\" type=text rows=30 cols=100 readonly=\"true\" value=\"",time.strftime(time.asctime()), "\"/>
"
print "</form>"
print "
"

print "
"
print "<form action=\"leave_message.py\" method=post>"
print "Leave a Message:
"
print "<textarea name=\"msgbox\"></textarea>
"
print "<input type=\"submit\" value=\"Submit\"/>
"
print "</form>"
print "
"

print "
"
print "<form action=\"youout.py\" method=post>"
print "Total player: "
print "<input type=text name=\"total_player\"/>
"
print "Unlucky N: "
print "<input type=text name=\"unlucky_n\" />
"
print "<input type=\"submit\" value=\"YouOut!\"/>
"
print "</form>"
print "
"

print ""
print ""

<commit_msg>add ranseq and user_info<commit_after>#!/usr/bin/python
#
# Starter.py
# Author: Zex <top_zlynch@yahoo.com>
#

import time

print "Content-Type: text/html\n\n"
print "<!DOCTYPE html>"
print ""

print ""
print "<title>Starter</title>"
print "<link href=\"/css/basic.css\" rel=\"stylesheet\" type=\"text/css\">"
print "<meta charset=\"UTF-8\">"
print ""

print ""
print "
"
print ""
print "
"

print "
"
print "<form action=\"hello_baby.py\" method=get>"
print "Action: "
print "<input type=text name=fr_name/>"
print "<input type=\"submit\" value=\"Do IT!\"/>
"
print "</form>"
print "
"

print "
"
print "<form action=\"upload_file.py\" method=post enctype=\"multipart/form-data\">"
print "Select file: "
print "<input type=file name=file_name/>"
print "<input type=\"submit\" value=\"Upload!\"/>
"
print "</form>"
print "
"

print "
"

print "
"
print ""
icecreams = [ "Milk", "Chocolate", "Strawberry", "Coconut", "Peanut" ]

print "<form action=\"icecream_box.py\" method=post>"
print "Icecream:
"

for i in icecreams:
    print "<input type=checkbox name=\"", i,"\" value=\"1\"/> ", i, "
"

print "<input type=\"submit\" value=\"Done!\"/>
"
print "</form>"
print "	"
print ""

flowers = [ "Almond Blossom", "Balsam", "Anther", "Camellia", "Azalea" ]

print "<form action=\"flower_man.py\" method=post>"
print "Flower:
"

for f in flowers:
    print "<input type=radio name=flower value=\"", f,"\"/> ", f,"
"

print "<input type=\"submit\" value=\"Done!\"/>
"
print "</form>"
print "	"
print "
"
print "
"

options = [ "Go", "Walk Away", "Close", "Laught" ]
print "
"
print "<form action=\"motion_trigger.py\" method=post>"
print "Motions:
"
print "<select name=option>"

for o in options:
    print "<option>" + o

print "</select>"
print "<input type=\"submit\" value=\"Done!\"/>
"
print "</form>"
print "
"

print "
"
print "Current time: "
print "<input id=\"cur_time\" type=text rows=30 cols=100 readonly=\"true\" value=\"",time.strftime(time.asctime()), "\"/>
"
print "</form>"
print "
"

print "
"
print "<form action=\"leave_message.py\" method=post>"
print "Leave a Message:
"
print "<textarea name=\"msgbox\"></textarea>
"
print "<input type=\"submit\" value=\"Submit\"/>
"
print "</form>"
print "
"

print "
"
print "
"
print ""
print "<form action=\"user_info.py\" method=get>"
print "<input type=\"submit\" value=\"User Info\"/>
"
print "</form>"
print "	"

print ""
print "<form action=\"ranseq.py\" method=get>"
print "<input type=\"submit\" value=\"Random Seq\"/>
"
print "</form>"
print "	"

for i in xrange(3):
	print ""
	print "<form action=\"hello_baby.py\" method=get>"
	print "<input type=\"submit\" value=\"TOBE CONTINUE...\"/>
"
	print "</form>"
	print "	"

print "
"
print "
"

print ""
print ""

<|endoftext|>


<commit_before>from chalktalk import app
from flask import render_template, request, url_for, redirect, abort
import chalktalk.database

database_url = 'sqlite:///dummy.db'
db = chalktalk.database.DatabaseManager(database_url)

app.secret_key = 'development key'

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/lecturelist/<course_id>')
def lecturelist(course_id):
    course = db.session.query(chalktalk.database.Course).get(course_id)
    return render_template('lecturelist.html', course=course)

@app.route('/createlecturelist')
def createlecturelist():
    return render_template('createlecturelist.html')

@app.route('/feedback/<lecture_id>', methods=['POST', 'GET'])
def feedbackform(lecture_id):
    lecture = db.session.query(chalktalk.database.Lecture).get(lecture_id)

    ##### NB : This should be the student giving the feedback, not just a
    ##### random student like now (just for testing)
    student = db.session.query(chalktalk.database.Student).first()
    #####

    if lecture is None:
        abort(404)
    subjects = db.session.query(chalktalk.database.Subject).filter_by(lecture_id=lecture.id).all()

    valid_form = True
    if request.method == 'POST':
        lecture_feedback = db.add_lecture_feedback(student, lecture, '')
        for subject in subjects:
            keys = ['{}_keyword'.format(subject.id),
                    '{}_rating'.format(subject.id),
                    '{}_comment'.format(subject.id)]
            if (keys[0] in request.form and
                    keys[1] in request.form and
                    keys[2] in request.form):
                db.add_subject_feedback(lecture_feedback,
                                        subject,
                                        request.form[keys[1]],
                                        request.form[keys[2]])
            else:
                valid_form = False
                break

        if valid_form:
            db.save_changes()
            return redirect('/lecturelist/{}'.format(lecture.course_id))

    return render_template('feedbackform.html',
                           lecture_id=lecture_id,
                           lecture=lecture,
                           subjects=subjects)

@app.route('/lecturefeedback/<lecture_id>')
def lecturefeedback(lecture_id):
    lecture = db.session.query(chalktalk.database.Lecture).filter_by(id=lecture_id).first()
    if lecture is None:
        abort(404)
    subjects = db.get_subject_values(lecture)
    return render_template('lecturefeedback.html', subjects=subjects)

@app.route('/lecturertest')
def lecturertest():
    return render_template('lecturertest.html')
<commit_msg>Use converters in flask app routes<commit_after>from chalktalk import app
from flask import render_template, request, url_for, redirect, abort
import chalktalk.database

database_url = 'sqlite:///dummy.db'
db = chalktalk.database.DatabaseManager(database_url)

app.secret_key = 'development key'

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/lecturelist/<int:course_id>')
def lecturelist(course_id):
    course = db.session.query(chalktalk.database.Course).get(course_id)
    return render_template('lecturelist.html', course=course)

@app.route('/createlecturelist')
def createlecturelist():
    return render_template('createlecturelist.html')

@app.route('/feedback/<int:lecture_id>', methods=['POST', 'GET'])
def feedbackform(lecture_id):
    lecture = db.session.query(chalktalk.database.Lecture).get(lecture_id)

    ##### NB : This should be the student giving the feedback, not just a
    ##### random student like now (just for testing)
    student = db.session.query(chalktalk.database.Student).first()
    #####

    if lecture is None:
        abort(404)
    subjects = db.session.query(chalktalk.database.Subject).filter_by(lecture_id=lecture.id).all()

    valid_form = True
    if request.method == 'POST':
        lecture_feedback = db.add_lecture_feedback(student, lecture, '')
        for subject in subjects:
            keys = ['{}_keyword'.format(subject.id),
                    '{}_rating'.format(subject.id),
                    '{}_comment'.format(subject.id)]
            if (keys[0] in request.form and
                    keys[1] in request.form and
                    keys[2] in request.form):
                db.add_subject_feedback(lecture_feedback,
                                        subject,
                                        request.form[keys[1]],
                                        request.form[keys[2]])
            else:
                valid_form = False
                break

        if valid_form:
            db.save_changes()
            return redirect('/lecturelist/{}'.format(lecture.course_id))

    return render_template('feedbackform.html',
                           lecture_id=lecture_id,
                           lecture=lecture,
                           subjects=subjects)

@app.route('/lecturefeedback/<int:lecture_id>')
def lecturefeedback(lecture_id):
    lecture = db.session.query(chalktalk.database.Lecture).filter_by(id=lecture_id).first()
    if lecture is None:
        abort(404)
    subjects = db.get_subject_values(lecture)
    return render_template('lecturefeedback.html', subjects=subjects)

@app.route('/lecturertest')
def lecturertest():
    return render_template('lecturertest.html')
<|endoftext|>


<commit_before>ELECTRUM_VERSION = "1.7.2"  # version of the client package
PROTOCOL_VERSION = '0.6'    # protocol version requested
SEED_VERSION     = 4        # bump this every time the seed generation is modified
TRANSLATION_ID   = 3992     # version of the wiki page
<commit_msg>Latest version of the wikia<commit_after>ELECTRUM_VERSION = "1.7.2"  # version of the client package
PROTOCOL_VERSION = '0.6'    # protocol version requested
SEED_VERSION     = 4        # bump this every time the seed generation is modified
TRANSLATION_ID   = 4012     # version of the wiki page
<|endoftext|>


<commit_before>#!/usr/bin/python3
## Copyright 2017 Marshall E. Giguere
##
##   Licensed 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.

version_tuple = (0,9,0)
VERSION = str(version_tuple[0]) + "." + str(version_tuple[1]) + "." + str(version_tuple[2])

# Maps CAP squadron/unit to Google organization path
orgUnitPath = {
    '000':"/000 - New Hampshire Wing",
    '001':"/001 - New Hampshire Wing",
    '010':"/010 - Portsmouth Squadron",
    '014':"/014 - Lebanon Squadron",
    '016':"/016 - Nashua Squadron",
    '032':"/032 - Concord Squadron",
    '037':"/037 - Highlanders Squadron - Rochester",
    '053':"/053 - Monadnock Squadron - Keene",
    '054':"/054 - Manchester Squadron",
    '056':"/056 - Hawk Squadron - Laconia",
    '075':"/075 - Whitefield",
    '801':"/801 - Humphrey Squadron - Nashua Cadets",
}

"""
MIMS - Member Information Management System.
       Google account synchronization between National and NH Wing.
       MIMS uses a combintation of MongoDB, Python, and the GAMADV-X
       Google Account Management tool. Requires G-Suite admin privileges.

History:
14Jul17 MEG Added NewSeniors class, NewMembers now basically an abstract class.
09Jun17 MEG Manager scans to leaf classes for jobs, new senior mailing list updater.
07Jun17 MEG Configurable log and job file paths, separate config file.
05Jun17 MEG Added suspend for expired members.
28May17 MEG Created.
"""
import os, sys
import datetime
import logging
import pymongo
from pymongo import *
from mims_conf import *

class Manager(object):
    """
    Manager is the super class/factory for all jobs to be done. Manager
    doesn't really do anything but maintain a list of jobs and the context
    for all jobs, i.e. MongoDB database connection, etc.

    Manager is a factory class.  It manages and returns subclasses that
    actually do the work, like adding new members to the rolls, or removing
    members that have fallen off the rolls.
    """
    __client = MongoClient( host=MIMS_HOST, port=MIMS_PORT )
    __DB = __client[ MIMS_DB ]
    __DB.authenticate( MIMSUSER, MIMSPASS )
    __TS = datetime.datetime.now().strftime("%Y%m%dT%H%M")
    def __init__( self ):
        self.myJobs = self.allSubClasses( type( self ))
        __TS = datetime.datetime.now().strftime("%Y%m%dT%H%M")
        self.logfileName = LogFilePath + self.name() + self.TS() + ".log"
        self.outfileName = JobFilePath + self.name() + self.TS() + ".job"

    def allSubClasses( self, cls ):
        """
        Recurses down the class tree and builds a list of all
        subclasses.  Factory subclass names are the functions that
        can be performed by MIMS.
        """
        allsubs = []
        for sub in cls.__subclasses__():
            allsubs.append( sub.__name__ )
            allsubs.extend( self.allSubClasses( sub ))
        return allsubs

    def DB(self):
        """
        Returns the current MongoDB object.
        """
        return self.__DB

    def close( self ):
        self.__client.close()

    def jobs( self ):
        """
        Returns a list of all known jobs, AKA my subclasses
        """
        return self.myJobs

    def name( self ):
        """
        Returns the name of the class.
        """
        return self.__class__.__name__

    @classmethod
    def TS( cls ):
        """
        Returns classes timestamp string.
        """
        return cls.__TS

    def job(self, job):
        """
        Job creates the requested subclass that does a particular job.
        The caller requests a job by sending the classes name string as
        the arguement 'job'.  Job returns the appropriate subclass instance,
        if the requested job cannot be found self is returned. This insures
        that something runable is returned.
        """
        if job in self.myJobs:
            return globals()[ job ]()   # create subclass job
        return self

class help( Manager):
    """
    Prints a help screen.
    """
    def __init__( self ):
        super().__init__()

    def run( self ):
        """
        Print help and forces exit.
        """
        print("Summary:")
        print( sys.argv[0], " <job>" )
        print( "Available Jobs:",MIMS.jobs() )

class NewMembers( Manager ):
    """
    AddMembers - scans the Member collection for active senior, cadet and patron
    members.  It then checks the Google collection to see if the member
    has an account on the wing Google system.  If the user is not found
    a new Google wing account GAM command is generated and added to a
    batch job file for later execution.
    Note: NewMembers does not add member to any groups.
    """
    def __init__(self):
        super().__init__()
        self.query = { "$or":[{'Type':'SENIOR'},{'Type':'CADET'},{'Type':'PATRON'}], "$and":[ {'MbrStatus':'ACTIVE'}] }
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )

    def givenName( self, f, m ):
        """
        Returns firstname + middle initial
        """
        mi = ''
        if m:
            mi = ' ' + m[0]
        return f + mi

    def familyName( self, l, s ):
        """
        Returns lastname + name suffix if any
        """
        if s:
            return l + ' ' + s
        return l

    def mkpasswd( self, m ):
        """
        Make a password for the new user m and return it.
        """
        return str(m['CAPID']) + '!' + m['NameFirst'][0]+ m['NameLast'][0]

    def run(self):
        gamcmdfmt = 'gam create user {}@nhwg.cap.gov externalid organization {:d} givenname "{}" familyname "{}" organizations department {} description {} primary orgunitpath "{}" password \'{}\' changepassword true'
        notifyfmt = ' notify {} subject "{}" message file "{}"'
        cur = self.DB().Member.find( self.query )
        with open( self.outfileName, 'w' ) as outfile:
            for m in cur:
                if ( (m['Unit'] == '000') or (m['Unit'] == "") ): continue  #Skip Unit 000 members
                g = self.DB().Google.find_one( {'externalIds':{'$elemMatch':{'value':m['CAPID']}}} )
                if ( g == None ): # if user does not exist make one
                    logging.info( "New User: %d %s %s %s",
                                  m['CAPID'],m['NameFirst'],
                                  m['NameLast'],
                                  "" if ( m['NameSuffix'] == None )
                                  else m['NameSuffix'] )
                    cmd = gamcmdfmt.format( m['CAPID'],
                                             m['CAPID'],
                                             self.givenName( m['NameFirst'],
                                                        m['NameMiddle'] ),
                                             self.familyName( m['NameLast'],
                                                         m['NameSuffix'] ),
                                             m['Unit'],
                                             m['Type'],
                                             orgUnitPath[m['Unit']],
                                             self.mkpasswd( m ))
                    # check for primary email to notify member
                    contact = self.DB().MbrContact.find_one({'CAPID':m['CAPID'],
                                              'Type':'EMAIL',
                                              'Priority':'PRIMARY'})
                    if contact:
                        cmd = cmd + notifyfmt.format( contact['Contact'],
                                                      "Welcome to your NH Wing account",
                                                      WELCOMEMSG )
                        print( cmd, file = outfile )
                    else: # do not issue account
                        logging.warn( "Member: %d %s %s %s does not have a primary email address.",
                                  m['CAPID'],m['NameFirst'],
                                  m['NameLast'],
                                  "" if ( m['NameSuffix'] == None )
                                  else m['NameSuffix'] )

class NewSeniors( NewMembers ):
    """
    Scans the Member table for Senior members not having Google accounts.
    Make a new account if the senior member is active, add to senior mailing
    list.
    """
    def __init__( self ):
        super().__init__()
        self.query = { 'Type':'SENIOR','MbrStatus':'ACTIVE' }
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )

    def run( self ):
        """
        Search for new senior members, create account, notify and add
        member to senior group.
        """
        gamcmdfmt = 'gam create user {}@nhwg.cap.gov externalid organization {:d} givenname "{}" familyname "{}" organizations department {} description {} primary orgunitpath "{}" password \'{}\' changepassword true'
        gamgroupfmt = 'gam user {}@nhwg.cap.gov add groups member seniors@nhwg.cap.gov'
        notifyfmt = ' notify {} subject "{}" message file "{}"'
        cur = self.DB().Member.find( self.query )
        with open( self.outfileName, 'w' ) as outfile:
            for m in cur:
                if ( m['Unit'] == '000' or m['Unit'] == "" ): continue  #Skip Unit 000 members
                g = self.DB().Google.find_one( {'externalIds':{'$elemMatch':{'value':m['CAPID']}}} )
                if ( g == None ): # if user does not exist make one
                    logging.info( "New User: %d %s %s %s",
                                  m['CAPID'],m['NameFirst'],
                                  m['NameLast'],
                                  "" if ( m['NameSuffix'] == None )
                                  else m['NameSuffix'] )
                    cmd = gamcmdfmt.format( m['CAPID'],
                                             m['CAPID'],
                                             self.givenName( m['NameFirst'],
                                                        m['NameMiddle'] ),
                                             self.familyName( m['NameLast'],
                                                         m['NameSuffix'] ),
                                             m['Unit'],
                                             m['Type'],
                                             orgUnitPath[ m['Unit'] ],
                                             self.mkpasswd( m ))
                    # check for primary email to notify member
                    contact = self.DB().MbrContact.find_one({'CAPID':m['CAPID'],
                                              'Type':'EMAIL',
                                              'Priority':'PRIMARY'})
                    if contact:
                        cmd = cmd + notifyfmt.format( contact['Contact'],
                                                      "Welcome to your NH Wing account",
                                                      WELCOMEMSG )
                        print( cmd, file = outfile )
                    else: # do not issue account
                        logging.warn( "Member: %d %s %s %s does not have a primary email address.",
                                  m['CAPID'],m['NameFirst'],
                                  m['NameLast'],
                                  "" if ( m['NameSuffix'] == None )
                                  else m['NameSuffix'] )
                    # add member to senior group mailing list
                    groupcmd = gamgroupfmt.format( m['CAPID'] )
                    logging.info( 'Member: %d added to senior mailing list.',
                                  m['CAPID'] )
                    print( groupcmd, file = outfile )

class PurgeMembers( Manager ):
    """
    RemoveUsers job - scans the Google user account collection by externalID
    i.e. CAPID and checks to see if the member is on the CAP rolls, if not
    a GAM command is generated to delete the user account from Google, and
    the command is added to a batch file for latter execution.
    """
    def __init__(self):
        super().__init__()
        self.query = { 'externalIds':{'$elemMatch':{'value':{'$ne':None}}}}
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )

    def run(self):
        """
        Runs the MongoDB query to find all members in Google not currently
        on the CAP rolls and produces a batch file to remove those member
        accounts from Google accounts using the GAM utility, deletes users
        record from Google MongoDB, although they will be gone after the next
        Google download anyway. It's just cleaner to remove the documents for
        subsequent runs.
        """
        gamcmdfmt = 'gam {} user {}'
        l = []  # list of members to remove
        #Scan all Google users
        g = self.DB().Google.find( self.query )
        for i in g:
            id = i['_id']
            capid = i['externalIds'][0]['value']
            m = self.DB().Member.find_one({'CAPID':capid})
            if m == None: # member nolonger on rolls
                l.append( i['primaryEmail'] )
                logging.info( "%s: %d %s", "Removed",
                              capid,
                              i['name']['fullName'])
                # delete user document from DB
                if ( DELETE_PURGED ):
                    print("Delete document:", "self.DB().Google.delete({'_id':",
                          id,"})" )
        l.sort()
        with open( self.outfileName, 'w' ) as outfile:
            for j in l:
                print( gamcmdfmt.format( PURGE_ACTION, j ),
                       file = outfile )

class Expired( Manager ):
    """
    Expired scans the Member collection for members whose membership
    has expired and issues a GAM command to suspend the users Wing
    account.
    """
    def __init__( self ):
        super().__init__()
        self.query = { 'MbrStatus':'EXPIRED' }
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )

    def run( self ):
        """
        Runs a query against the Member collection looking for expired
        members, if found issues a GAM command to suspend the Wing user
        Google account.  The account is NOT deleted and may be reactivated
        by any sys admin.
        """
        gamcmdfmt = "gam {} user {}"
        cur = self.DB().Member.find( self.query )
        with open( self.outfileName, 'w' ) as outfile:
            for m in cur:
                g = self.DB().Google.find_one(
                    {'externalIds':{'$elemMatch':{'value':m[ 'CAPID' ]}}} )
                if ( g ):
                    logging.info( "%s %d %s %s %s", "Suspended:",
                                  m[ 'CAPID' ], m[ 'NameFirst' ],
                                  m[ 'NameLast' ],
                                  "" if ( m[ 'NameSuffix' ] == None )
                                  else m[ 'NameSuffix' ] )
                    print( gamcmdfmt.format( EXPIRED_ACTION,
                                             g[ 'primaryEmail' ]),
                           file = outfile )

class ListManager( Manager ):
    """
    Root class of all mailing list management subclasses/jobs.
    This is just a base class doesn't do anything, subclasses
    are the real actors.
    """
    def __init__(self):
        super().__init__()

    def run( self ):
        """
        Doesn't do anything just a placeholder for subclasses.
        """
        return

    def isGroupMember( self, groups, group ):
        """
        Search the users groups list to see if group email address is in
        the members list of groups. If not found return None.
        """
        for g in groups:
            if ( g['email'] == group ):
                return True
        return None # not member of group

class SeniorListChecker( ListManager ):
    """
    Scan Google users for senior members, check to see if they are on the
    senior mailing list, if not add them.
    """
    def __init__( self ):
        super().__init__()
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )
        self.query = {'organizations':{'$elemMatch':{'description':"SENIOR"}}}

    def run( self ):
        """
        Scan for senior members and add them to the senior mailing list
        """
        cur = self.DB().Google.find( self.query )
        gamcmdfmt = 'gam user {} add groups member {}'
        with open( self.outfileName, 'w' ) as outfile:
            for m in cur:
                primaryEmail = m['primaryEmail']
                if not self.isGroupMember( m['groups'], "seniors@nhwg.cap.gov"):
                    logging.info( "%s %s", "Senior mailing list Add:", primaryEmail )
                    print( gamcmdfnt.format( primaryEmail,
                           "seniors@nhwg.cap.gov"),
                           file = outfile )

# Create the base object for all jobs
# MIMS is the factory base class object
MIMS = Manager()

def main():
    """
    Main - MIMS produces a stream of GAM commands to create, remove or
    suspend users as a batch file.  The batch file can then be scheduled
    to run at a later time.
    """
    # Logging on/off to stderr
    LOGGING = os.environ.get( 'LOGGING' )

    job = MIMS.Job( sys.argv[1] )
    job.run()
    job.DB().logout()
    MIMS.close()

###########################################
#
#       Invoke main function
#
###########################################
if __name__ == "__main__" :
    sys.exit( main() )
<commit_msg>Update mims.py<commit_after>#!/usr/bin/python3
## Copyright 2017 Marshall E. Giguere
##
##   Licensed 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.

version_tuple = (0,9,0)
VERSION = str(version_tuple[0]) + "." + str(version_tuple[1]) + "." + str(version_tuple[2])

# Maps CAP squadron/unit to Google organization path
orgUnitPath = {
    '000':"/000 - New Hampshire Wing",
    '001':"/001 - New Hampshire Wing",
    '010':"/010 - Portsmouth Squadron",
    '014':"/014 - Lebanon Squadron",
    '016':"/016 - Nashua Squadron",
    '032':"/032 - Concord Squadron",
    '037':"/037 - Highlanders Squadron - Rochester",
    '053':"/053 - Monadnock Squadron - Keene",
    '054':"/054 - Manchester Squadron",
    '056':"/056 - Hawk Squadron - Laconia",
    '075':"/075 - Whitefield",
    '801':"/801 - Humphrey Squadron - Nashua Cadets",
}

"""
MIMS - Member Information Management System.
       Google account synchronization between National and NH Wing.
       MIMS uses a combintation of MongoDB, Python, and the GAMADV-X
       Google Account Management tool. Requires G-Suite admin privileges.

History:
14Jul17 MEG Added NewSeniors class, NewMembers now basically an abstract class.
09Jun17 MEG Manager scans to leaf classes for jobs, new senior mailing list updater.
07Jun17 MEG Configurable log and job file paths, separate config file.
05Jun17 MEG Added suspend for expired members.
28May17 MEG Created.
"""
import os, sys
import datetime
import logging
import pymongo
from pymongo import *
from mims_conf import *

class Manager(object):
    """
    Manager is the super class/factory for all jobs to be done. Manager
    doesn't really do anything but maintain a list of jobs and the context
    for all jobs, i.e. MongoDB database connection, etc.

    Manager is a factory class.  It manages and returns subclasses that
    actually do the work, like adding new members to the rolls, or removing
    members that have fallen off the rolls.
    """
    __client = MongoClient( host=MIMS_HOST, port=MIMS_PORT )
    __DB = __client[ MIMS_DB ]
    __DB.authenticate( MIMSUSER, MIMSPASS )
    __TS = datetime.datetime.now().strftime("%Y%m%dT%H%M")
    def __init__( self ):
        self.myJobs = self.allSubClasses( type( self ))
        __TS = datetime.datetime.now().strftime("%Y%m%dT%H%M")
        self.logfileName = LogFilePath + self.name() + self.TS() + ".log"
        self.outfileName = JobFilePath + self.name() + self.TS() + ".job"

    def allSubClasses( self, cls ):
        """
        Recurses down the class tree and builds a list of all
        subclasses.  Factory subclass names are the functions that
        can be performed by MIMS.
        """
        allsubs = []
        for sub in cls.__subclasses__():
            allsubs.append( sub.__name__ )
            allsubs.extend( self.allSubClasses( sub ))
        return allsubs

    def DB(self):
        """
        Returns the current MongoDB object.
        """
        return self.__DB

    def close( self ):
        self.__client.close()

    def jobs( self ):
        """
        Returns a list of all known jobs, AKA my subclasses
        """
        return self.myJobs

    def name( self ):
        """
        Returns the name of the class.
        """
        return self.__class__.__name__

    @classmethod
    def TS( cls ):
        """
        Returns classes timestamp string.
        """
        return cls.__TS

    def job(self, job):
        """
        Job creates the requested subclass that does a particular job.
        The caller requests a job by sending the classes name string as
        the arguement 'job'.  Job returns the appropriate subclass instance,
        if the requested job cannot be found self is returned. This insures
        that something runable is returned.
        """
        if job in self.myJobs:
            return globals()[ job ]()   # create subclass job
        return self

class help( Manager):
    """
    Prints a help screen.
    """
    def __init__( self ):
        super().__init__()

    def run( self ):
        """
        Print help and forces exit.
        """
        print("Summary:")
        print( sys.argv[0], " <job>" )
        print( "Available Jobs:",MIMS.jobs() )

class NewMembers( Manager ):
    """
    AddMembers - scans the Member collection for active senior, cadet and patron
    members.  It then checks the Google collection to see if the member
    has an account on the wing Google system.  If the user is not found
    a new Google wing account GAM command is generated and added to a
    batch job file for later execution.
    Note: NewMembers does not add member to any groups.
    """
    def __init__(self):
        super().__init__()
        self.query = { "$or":[{'Type':'SENIOR'},{'Type':'CADET'},{'Type':'PATRON'}], "$and":[ {'MbrStatus':'ACTIVE'}] }
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )

    def givenName( self, f, m ):
        """
        Returns firstname + middle initial
        """
        mi = ''
        if m:
            mi = ' ' + m[0]
        return f + mi

    def familyName( self, l, s ):
        """
        Returns lastname + name suffix if any
        """
        if s:
            return l + ' ' + s
        return l

    def mkpasswd( self, m ):
        """
        Make a password for the new user m and return it.
        """
        return str(m['CAPID']) + '!' + m['NameFirst'][0]+ m['NameLast'][0]

    def run(self):
        print( 'NewMembers is an abstract class, provides no services' )
        return self

class NewSeniors( NewMembers ):
    """
    Scans the Member table for Senior members not having Google accounts.
    Make a new account if the senior member is active, add to senior mailing
    list.
    """
    def __init__( self ):
        super().__init__()
        self.query = { 'Type':'SENIOR','MbrStatus':'ACTIVE' }
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )

    def run( self ):
        """
        Search for new senior members, create account, notify and add
        member to senior group.
        """
        gamcmdfmt = 'gam create user {}@nhwg.cap.gov externalid organization {:d} givenname "{}" familyname "{}" organizations department {} description {} primary orgunitpath "{}" password \'{}\' changepassword true'
        gamgroupfmt = 'gam user {}@nhwg.cap.gov add groups member seniors@nhwg.cap.gov'
        notifyfmt = ' notify {} subject "{}" message file "{}"'
        cur = self.DB().Member.find( self.query )
        with open( self.outfileName, 'w' ) as outfile:
            for m in cur:
                if ( m['Unit'] == '000' or m['Unit'] == "" ): continue  #Skip Unit 000 members
                g = self.DB().Google.find_one( {'externalIds':{'$elemMatch':{'value':m['CAPID']}}} )
                if ( g == None ): # if user does not exist make one
                    logging.info( "New User: %d %s %s %s",
                                  m['CAPID'],m['NameFirst'],
                                  m['NameLast'],
                                  "" if ( m['NameSuffix'] == None )
                                  else m['NameSuffix'] )
                    cmd = gamcmdfmt.format( m['CAPID'],
                                             m['CAPID'],
                                             self.givenName( m['NameFirst'],
                                                        m['NameMiddle'] ),
                                             self.familyName( m['NameLast'],
                                                         m['NameSuffix'] ),
                                             m['Unit'],
                                             m['Type'],
                                             orgUnitPath[ m['Unit'] ],
                                             self.mkpasswd( m ))
                    # check for primary email to notify member
                    contact = self.DB().MbrContact.find_one({'CAPID':m['CAPID'],
                                              'Type':'EMAIL',
                                              'Priority':'PRIMARY'})
                    if contact:
                        cmd = cmd + notifyfmt.format( contact['Contact'],
                                                      "Welcome to your NH Wing account",
                                                      WELCOMEMSG )
                        print( cmd, file = outfile )
                    else: # do not issue account
                        logging.warn( "Member: %d %s %s %s does not have a primary email address.",
                                  m['CAPID'],m['NameFirst'],
                                  m['NameLast'],
                                  "" if ( m['NameSuffix'] == None )
                                  else m['NameSuffix'] )
                    # add member to senior group mailing list
                    groupcmd = gamgroupfmt.format( m['CAPID'] )
                    logging.info( 'Member: %d added to senior mailing list.',
                                  m['CAPID'] )
                    print( groupcmd, file = outfile )

class PurgeMembers( Manager ):
    """
    RemoveUsers job - scans the Google user account collection by externalID
    i.e. CAPID and checks to see if the member is on the CAP rolls, if not
    a GAM command is generated to delete the user account from Google, and
    the command is added to a batch file for latter execution.
    """
    def __init__(self):
        super().__init__()
        self.query = { 'externalIds':{'$elemMatch':{'value':{'$ne':None}}}}
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )

    def run(self):
        """
        Runs the MongoDB query to find all members in Google not currently
        on the CAP rolls and produces a batch file to remove those member
        accounts from Google accounts using the GAM utility, deletes users
        record from Google MongoDB, although they will be gone after the next
        Google download anyway. It's just cleaner to remove the documents for
        subsequent runs.
        """
        gamcmdfmt = 'gam {} user {}'
        l = []  # list of members to remove
        #Scan all Google users
        g = self.DB().Google.find( self.query )
        for i in g:
            id = i['_id']
            capid = i['externalIds'][0]['value']
            m = self.DB().Member.find_one({'CAPID':capid})
            if m == None: # member nolonger on rolls
                l.append( i['primaryEmail'] )
                logging.info( "%s: %d %s", "Removed",
                              capid,
                              i['name']['fullName'])
                # delete user document from DB
                if ( DELETE_PURGED ):
                    print("Delete document:", "self.DB().Google.delete({'_id':",
                          id,"})" )
        l.sort()
        with open( self.outfileName, 'w' ) as outfile:
            for j in l:
                print( gamcmdfmt.format( PURGE_ACTION, j ),
                       file = outfile )

class Expired( Manager ):
    """
    Expired scans the Member collection for members whose membership
    has expired and issues a GAM command to suspend the users Wing
    account.
    """
    def __init__( self ):
        super().__init__()
        self.query = { 'MbrStatus':'EXPIRED' }
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )

    def run( self ):
        """
        Runs a query against the Member collection looking for expired
        members, if found issues a GAM command to suspend the Wing user
        Google account.  The account is NOT deleted and may be reactivated
        by any sys admin.
        """
        gamcmdfmt = "gam {} user {}"
        cur = self.DB().Member.find( self.query )
        with open( self.outfileName, 'w' ) as outfile:
            for m in cur:
                g = self.DB().Google.find_one(
                    {'externalIds':{'$elemMatch':{'value':m[ 'CAPID' ]}}} )
                if ( g ):
                    logging.info( "%s %d %s %s %s", "Suspended:",
                                  m[ 'CAPID' ], m[ 'NameFirst' ],
                                  m[ 'NameLast' ],
                                  "" if ( m[ 'NameSuffix' ] == None )
                                  else m[ 'NameSuffix' ] )
                    print( gamcmdfmt.format( EXPIRED_ACTION,
                                             g[ 'primaryEmail' ]),
                           file = outfile )

class ListManager( Manager ):
    """
    Root class of all mailing list management subclasses/jobs.
    This is just a base class doesn't do anything, subclasses
    are the real actors.
    """
    def __init__(self):
        super().__init__()

    def run( self ):
        """
        Doesn't do anything just a placeholder for subclasses.
        """
        return

    def isGroupMember( self, groups, group ):
        """
        Search the users groups list to see if group email address is in
        the members list of groups. If not found return None.
        """
        for g in groups:
            if ( g['email'] == group ):
                return True
        return None # not member of group

class SeniorListChecker( ListManager ):
    """
    Scan Google users for senior members, check to see if they are on the
    senior mailing list, if not add them.
    """
    def __init__( self ):
        super().__init__()
        logging.basicConfig( filename = self.logfileName, filemode = 'w',
                             level = logging.DEBUG )
        self.query = {'organizations':{'$elemMatch':{'description':"SENIOR"}}}

    def run( self ):
        """
        Scan for senior members and add them to the senior mailing list
        """
        cur = self.DB().Google.find( self.query )
        gamcmdfmt = 'gam user {} add groups member {}'
        with open( self.outfileName, 'w' ) as outfile:
            for m in cur:
                primaryEmail = m['primaryEmail']
                if not self.isGroupMember( m['groups'], "seniors@nhwg.cap.gov"):
                    logging.info( "%s %s", "Senior mailing list Add:", primaryEmail )
                    print( gamcmdfnt.format( primaryEmail,
                           "seniors@nhwg.cap.gov"),
                           file = outfile )

# Create the base object for all jobs
# MIMS is the factory base class object
MIMS = Manager()

def main():
    """
    Main - MIMS produces a stream of GAM commands to create, remove or
    suspend users as a batch file.  The batch file can then be scheduled
    to run at a later time.
    """
    # Logging on/off to stderr
    LOGGING = os.environ.get( 'LOGGING' )

    job = MIMS.Job( sys.argv[1] )
    job.run()
    job.DB().logout()
    MIMS.close()

###########################################
#
#       Invoke main function
#
###########################################
if __name__ == "__main__" :
    sys.exit( main() )
<|endoftext|>


<commit_before>from .. import Server
import logging

class MongoDataNode(Server):

    log = logging.getLogger('Servers.MongoDataNode')
    log.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    formatter = logging.Formatter(
            '%(asctime)s [%(name)s] %(levelname)s: %(message)s',
            datefmt = '%H:%M:%S')
    ch.setFormatter(formatter)
    log.addHandler(ch)

    def __init__(self, dry = None, verbose = None, size = None, cluster = None,
                    environment = None, ami = None, region = None, role = None,
                    keypair = None, availability_zone = None,
                    security_groups = None, block_devices = None,
                    replica_set = None, replica_set_index = None):

        super(MongoDataNode, self).__init__(dry, verbose, size, cluster,
                                            environment, ami, region, role,
                                            keypair, availability_zone,
                                            security_groups, block_devices)

        self.replica_set = replica_set
        self.replica_set_index = replica_set_index

    def configure(self):

        super(MongoDataNode, self).configure()

        if self.replica_set is None:
            self.log.warn('No replica set provided')
            self.replica_set = 1

        self.log.info('Using replica set {set}'.format(set=self.replica_set))

        if self.replica_set_index is None:
            self.log.warn('No replica set set index provided')
            self.replica_set_index = 1

        self.log.info('Using replica set index {index}'.format(
                        index=self.replica_set_index))

    @property
    def name(self):

        try:
            return self.unique_name
        except Exception:
            pass

        template = '{envcl}-rs{set}-{zone}-{index}'
        name = template.format(envcl=self.envcl, set=self.replica_set,
                                zone=self.availability_zone[-1:],
                                index=self.replica_set_index)

        self.unique_name = name

        self.log.info('Using node name {name}'.format(name=name))

        return name

    def bake(self):

        chef_path = os.path.expanduser('~/.chef')
        chef_api = chef.autoconfigure(chef_path)

        node = chef.Node.create(self.name, api=chef_api)
        node.chef_environment = self.environment

        if node.chef_environment == 'prod':
            node.attributes.set_dotted('hudl_ebs.volumes', [
                {
                    'user': 'mongod',
                    'group': 'mongod',
                    'size': 10,
                    'iops': 0,
                    'device': '/dev/xvdg',
                    'mount': '/volr/journal'},
                {
                    'user': 'mongod',
                    'group': 'mongod',
                    'size': 400,
                    'iops': 3000,
                    'device': '/dev/xvdf',
                    'mount': '/volr'
                }
            ])
        else:
            node.attributes.set_dotted('hudl_ebs.volumes', [
                {
                    'user': 'mongod',
                    'group': 'mongod',
                    'size': 10,
                    'iops': 0,
                    'device': '/dev/xvdg',
                    'mount': '/volr/journal'
                },
                {
                    'user': 'mongod',
                    'group': 'mongod',
                    'size': 400,
                    'iops': 0,
                    'device': '/dev/xvdf',
                    'mount': '/volr'
                }
            ])
        node.save(api=chef_api)
<commit_msg>Set the cluster_name and replica_set_name attributes<commit_after>from .. import Server
import logging

class MongoDataNode(Server):

    log = logging.getLogger('Servers.MongoDataNode')
    log.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    formatter = logging.Formatter(
            '%(asctime)s [%(name)s] %(levelname)s: %(message)s',
            datefmt = '%H:%M:%S')
    ch.setFormatter(formatter)
    log.addHandler(ch)

    def __init__(self, dry = None, verbose = None, size = None, cluster = None,
                    environment = None, ami = None, region = None, role = None,
                    keypair = None, availability_zone = None,
                    security_groups = None, block_devices = None,
                    replica_set = None, replica_set_index = None):

        super(MongoDataNode, self).__init__(dry, verbose, size, cluster,
                                            environment, ami, region, role,
                                            keypair, availability_zone,
                                            security_groups, block_devices)

        self.replica_set = replica_set
        self.replica_set_index = replica_set_index

    def configure(self):

        super(MongoDataNode, self).configure()

        if self.replica_set is None:
            self.log.warn('No replica set provided')
            self.replica_set = 1

        self.log.info('Using replica set {set}'.format(set=self.replica_set))

        if self.replica_set_index is None:
            self.log.warn('No replica set set index provided')
            self.replica_set_index = 1

        self.log.info('Using replica set index {index}'.format(
                        index=self.replica_set_index))

    @property
    def name(self):

        try:
            return self.unique_name
        except Exception:
            pass

        template = '{envcl}-rs{set}-{zone}-{index}'
        name = template.format(envcl=self.envcl, set=self.replica_set,
                                zone=self.availability_zone[-1:],
                                index=self.replica_set_index)

        self.unique_name = name

        self.log.info('Using node name {name}'.format(name=name))

        return name

    def bake(self):

        chef_path = os.path.expanduser('~/.chef')
        chef_api = chef.autoconfigure(chef_path)

        node = chef.Node.create(self.name, api=chef_api)
        node.chef_environment = self.environment

        if node.chef_environment == 'prod':
            node.attributes.set_dotted('hudl_ebs.volumes', [
                {
                    'user': 'mongod',
                    'group': 'mongod',
                    'size': 10,
                    'iops': 0,
                    'device': '/dev/xvdg',
                    'mount': '/volr/journal'},
                {
                    'user': 'mongod',
                    'group': 'mongod',
                    'size': 400,
                    'iops': 3000,
                    'device': '/dev/xvdf',
                    'mount': '/volr'
                }
            ])
        else:
            node.attributes.set_dotted('hudl_ebs.volumes', [
                {
                    'user': 'mongod',
                    'group': 'mongod',
                    'size': 10,
                    'iops': 0,
                    'device': '/dev/xvdg',
                    'mount': '/volr/journal'
                },
                {
                    'user': 'mongod',
                    'group': 'mongod',
                    'size': 400,
                    'iops': 0,
                    'device': '/dev/xvdf',
                    'mount': '/volr'
                }
            ])

        cluster_name = self.cluster.split('-')[0]
        replica_set = 'rs' + str(self.replica_set)

        node.attributes.set_dotted('mongodb.cluster_name', cluster_name)
        node.attributes.set_dotted('mongodb.replicaset_name', replica_set)

        node.save(api=chef_api)
<|endoftext|>


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

#This is free and unencumbered software released into the public domain.
#
#Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
#software, either in source code form or as a compiled binary, for any purpose,
#commercial or non-commercial, and by any means.
#
#In jurisdictions that recognise copyright laws, the author or authors of this
#software dedicate any and all copyright interest in the software to the public
#domain. We make this dedication for the benefit of the public at large and to
#the detriment of our heirs and successors. We intend this dedication to be an
#overt act of relinquishment in perpetuity of all present and future rights to
#this software under copyright law.
#
#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 NON-INFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS 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.
#
#For more information, please refer to <https://unlicense.org/>.

"""
An API for logging messages.

This defines a number of logging levels, and allows the application to log
messages with those levels.
"""

import enum #To define the logging importance levels.

import loggertype.loggerregistrar #To get the logger plug-ins to log with.

class Log:
	class Level(enum.Enum):
		"""
		Enumerates the logging importance levels.
		"""

		ERROR = 1
		"""
		For logging fatal errors that will crash the program.
		"""

		CRITICAL = 2
		"""
		For logging fatal errors that will crash the current operation.
		"""

		WARNING = 3
		"""
		For logging events that are probably not going the way the user
		intended.
		"""

		INFO = 4
		"""
		For logging events.

		At least all events that got initiated from an external source must be
		logged with this level, such as user input.
		"""

		DEBUG = 5
		"""
		Information that might be useful for a debugger to know.
		"""

	@staticmethod
	def critical(message, title="Critical", **kwargs):
		"""
		.. function:: critical(message[, title][, key=value]*)
		Logs a new critical message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.critical(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)

	@staticmethod
	def debug(message, title="Debug", **kwargs):
		"""
		.. function:: debug(message[, title][, key=value]*)
		Logs a new debug message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.debug(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)

	@staticmethod
	def error(message, title="Error", **kwargs):
		"""
		.. function:: error(message[, title][, key=value]*)
		Logs a new error message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.error(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)

	@staticmethod
	def info(message, title="Info", **kwargs):
		"""
		.. function:: info(message[, title][, key=value]*)
		Logs a new information message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.info(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)

	@staticmethod
	def set_log_levels(levels, logger_name=None):
		"""
		.. function:: setLogLevels(levels[, loggerName])
		Sets the log levels that are logged by the loggers.

		The logger(s) will only acquire log messages with importance levels that
		are in the list specified by the last call to this function.

		If given a logger name, the log levels are only set for the specified
		logger. If not given a name, the log levels are set for all loggers.

		:param levels: A list of log levels that the logger(s) will log.
		:param logger_name: The identifier of a logger plug-in if setting the
			levels for a specific logger, or None if setting the levels for all
			loggers.
		"""
		if logger_name: #If given a specific logger name, set the log levels only for that logger.
			logger = luna.plugins.get_logger(logger_name)
			if not logger:
				warning("Logger {name} doesn't exist.", name=logger_name)
				return
			logger.set_levels(levels)
		else: #If not given any specific logger name, set the log levels for all loggers.
			for logger in loggertype.loggerregistrar.get_all_loggers():
				logger.set_levels(levels)

	@staticmethod
	def warning(message, title="Warning", **kwargs):
		"""
		.. function:: warning(message[, title][, key=value]*)
		Logs a new warning message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.warning(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)<commit_msg>Add documentation for Log API class<commit_after>#!/usr/bin/env python

#This is free and unencumbered software released into the public domain.
#
#Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
#software, either in source code form or as a compiled binary, for any purpose,
#commercial or non-commercial, and by any means.
#
#In jurisdictions that recognise copyright laws, the author or authors of this
#software dedicate any and all copyright interest in the software to the public
#domain. We make this dedication for the benefit of the public at large and to
#the detriment of our heirs and successors. We intend this dedication to be an
#overt act of relinquishment in perpetuity of all present and future rights to
#this software under copyright law.
#
#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 NON-INFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS 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.
#
#For more information, please refer to <https://unlicense.org/>.

"""
An API for logging messages.

This defines a number of logging levels, and allows the application to log
messages with those levels.
"""

import enum #To define the logging importance levels.

import loggertype.loggerregistrar #To get the logger plug-ins to log with.

class Log:
	"""
	An API for logging messages.
	"""

	class Level(enum.Enum):
		"""
		Enumerates the logging importance levels.
		"""

		ERROR = 1
		"""
		For logging fatal errors that will crash the program.
		"""

		CRITICAL = 2
		"""
		For logging fatal errors that will crash the current operation.
		"""

		WARNING = 3
		"""
		For logging events that are probably not going the way the user
		intended.
		"""

		INFO = 4
		"""
		For logging events.

		At least all events that got initiated from an external source must be
		logged with this level, such as user input.
		"""

		DEBUG = 5
		"""
		Information that might be useful for a debugger to know.
		"""

	@staticmethod
	def critical(message, title="Critical", **kwargs):
		"""
		.. function:: critical(message[, title][, key=value]*)
		Logs a new critical message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.critical(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)

	@staticmethod
	def debug(message, title="Debug", **kwargs):
		"""
		.. function:: debug(message[, title][, key=value]*)
		Logs a new debug message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.debug(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)

	@staticmethod
	def error(message, title="Error", **kwargs):
		"""
		.. function:: error(message[, title][, key=value]*)
		Logs a new error message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.error(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)

	@staticmethod
	def info(message, title="Info", **kwargs):
		"""
		.. function:: info(message[, title][, key=value]*)
		Logs a new information message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.info(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)

	@staticmethod
	def set_log_levels(levels, logger_name=None):
		"""
		.. function:: setLogLevels(levels[, loggerName])
		Sets the log levels that are logged by the loggers.

		The logger(s) will only acquire log messages with importance levels that
		are in the list specified by the last call to this function.

		If given a logger name, the log levels are only set for the specified
		logger. If not given a name, the log levels are set for all loggers.

		:param levels: A list of log levels that the logger(s) will log.
		:param logger_name: The identifier of a logger plug-in if setting the
			levels for a specific logger, or None if setting the levels for all
			loggers.
		"""
		if logger_name: #If given a specific logger name, set the log levels only for that logger.
			logger = luna.plugins.get_logger(logger_name)
			if not logger:
				warning("Logger {name} doesn't exist.", name=logger_name)
				return
			logger.set_levels(levels)
		else: #If not given any specific logger name, set the log levels for all loggers.
			for logger in loggertype.loggerregistrar.get_all_loggers():
				logger.set_levels(levels)

	@staticmethod
	def warning(message, title="Warning", **kwargs):
		"""
		.. function:: warning(message[, title][, key=value]*)
		Logs a new warning message with all loggers.

		:param message: The message of the log entry.
		:param title: A title for the entry.
		:param kwargs: Key-word arguments. These are inserted in the message
			body. The value of a key-word argument will be put in place of the
			key surrounded by brackets. See the Python documentation for
			``str.format`` for more details.
		"""
		substituted = message.format(**kwargs) #Substitute all arguments into the message.
		loggers = loggertype.loggerregistrar.get_all_loggers()
		for logger in loggers:
			logger.warning(substituted, title)
		if not loggers: #There are no loggers.
			print(title + ": " + substituted)<|endoftext|>


<commit_before>from __future__ import absolute_import, division, print_function

from datetime import datetime
from urlparse import urlparse

from .base import Vcs, RevisionResult, BufferParser

LOG_FORMAT = '%H\x01%an <%ae>\x01%at\x01%cn <%ce>\x01%ct\x01%P\x01%B\x02'

class GitVcs(Vcs):
    binary_path = 'git'

    def get_default_env(self):
        return {
            'GIT_SSH': self.ssh_connect_path,
        }

    @property
    def remote_url(self):
        if self.url.startswith(('ssh:', 'http:', 'https:')):
            parsed = urlparse(self.url)
            url = '%s://%s@%s/%s' % (
                parsed.scheme,
                parsed.username or self.username or 'git',
                parsed.hostname + (':%s' % (parsed.port,) if parsed.port else ''),
                parsed.path.lstrip('/'),
            )
        else:
            url = self.url
        return url

    def branches_for_commit(self, id):
        results = self.run(['branch', '-a', '--contains', id])
        return [r[2:].strip() for r in results.splitlines()]

    def run(self, cmd, **kwargs):
        cmd = [self.binary_path] + cmd
        return super(GitVcs, self).run(cmd, **kwargs)

    def clone(self):
        self.run(['clone', '--mirror', self.remote_url, self.path])

    def update(self):
        self.run(['fetch', '--all'])

    def log(self, parent=None, limit=100):
        # TODO(dcramer): we should make this streaming
        cmd = ['log', '--all', '--pretty=format:%s' % (LOG_FORMAT,)]
        if parent:
            cmd.append(parent)
        if limit:
            cmd.append('-n %d' % (limit,))
        result = self.run(cmd)

        for chunk in BufferParser(result, '\x02'):
            (sha, author, author_date, committer, committer_date,
             parents, message) = chunk.split('\x01')

            # sha may have a trailing newline due to git log adding it
            sha = sha.lstrip('\n')

            parents = filter(bool, parents.split(' '))

            author_date = datetime.utcfromtimestamp(float(author_date))
            committer_date = datetime.utcfromtimestamp(float(committer_date))

            yield RevisionResult(
                id=sha,
                author=author,
                committer=committer,
                author_date=author_date,
                committer_date=committer_date,
                parents=parents,
                branches=self.branches_for_commit(sha),
                message=message,
            )

    def export(self, id):
        cmd = ['log', '-n 1', '-p', '--pretty="%b"', id]
        result = self.run(cmd)[4:]
        return result
<commit_msg>strip prefix<commit_after>from __future__ import absolute_import, division, print_function

from datetime import datetime
from urlparse import urlparse

from .base import Vcs, RevisionResult, BufferParser

LOG_FORMAT = '%H\x01%an <%ae>\x01%at\x01%cn <%ce>\x01%ct\x01%P\x01%B\x02'

class GitVcs(Vcs):
    binary_path = 'git'

    def get_default_env(self):
        return {
            'GIT_SSH': self.ssh_connect_path,
        }

    @property
    def remote_url(self):
        if self.url.startswith(('ssh:', 'http:', 'https:')):
            parsed = urlparse(self.url)
            url = '%s://%s@%s/%s' % (
                parsed.scheme,
                parsed.username or self.username or 'git',
                parsed.hostname + (':%s' % (parsed.port,) if parsed.port else ''),
                parsed.path.lstrip('/'),
            )
        else:
            url = self.url
        return url

    def branches_for_commit(self, id):
        results = []
        output = self.run(['branch', '-a', '--contains', id])
        for result in output.splitlines():
            # HACK(dcramer): is there a better way around removing the prefix?
            result = result[2:].strip().lstrip('remotes/origin/')
            results.append(result)
        return results

    def run(self, cmd, **kwargs):
        cmd = [self.binary_path] + cmd
        return super(GitVcs, self).run(cmd, **kwargs)

    def clone(self):
        self.run(['clone', '--mirror', self.remote_url, self.path])

    def update(self):
        self.run(['fetch', '--all'])

    def log(self, parent=None, limit=100):
        # TODO(dcramer): we should make this streaming
        cmd = ['log', '--all', '--pretty=format:%s' % (LOG_FORMAT,)]
        if parent:
            cmd.append(parent)
        if limit:
            cmd.append('-n %d' % (limit,))
        result = self.run(cmd)

        for chunk in BufferParser(result, '\x02'):
            (sha, author, author_date, committer, committer_date,
             parents, message) = chunk.split('\x01')

            # sha may have a trailing newline due to git log adding it
            sha = sha.lstrip('\n')

            parents = filter(bool, parents.split(' '))

            author_date = datetime.utcfromtimestamp(float(author_date))
            committer_date = datetime.utcfromtimestamp(float(committer_date))

            yield RevisionResult(
                id=sha,
                author=author,
                committer=committer,
                author_date=author_date,
                committer_date=committer_date,
                parents=parents,
                branches=self.branches_for_commit(sha),
                message=message,
            )

    def export(self, id):
        cmd = ['log', '-n 1', '-p', '--pretty="%b"', id]
        result = self.run(cmd)[4:]
        return result
<|endoftext|>