Does Python 3 have LDAP module? - python

I am porting some Java code to Python and we would like to use Python 3 but I can't find LDAP module for Python 3 in Windows.
This is forcing us to use 2.6 version and it is bothersome as rest of the code is already in 3.0 format.

You may use ldap3 module (formerly known as python3-ldap), it runs on python3 really well and requires no external C dependances. Also it can correctly handle both unicode and byte data in ldap records (in early versions there was a trouble with jpegPhoto field, now everything is fine)

If you're running this on Windows, you can get LDAP to work in Python 3.1 by using the ADO access method via Mark Hammond's PyWin32.
To test this, I installed ActiveState Python 3.1, then installed PyWin32 for Python 3.1
http://sourceforge.net/projects/pywin32/files/
I was then able to run LDAP queries using a module I wrote that is based on this LDAP code from the ActiveState Python Cookbook:
Recipe 511451: Dump all Active Directory Information using LDAP scripting by Manuel Garcia
http://code.activestate.com/recipes/511451/
although now that I look at it I realize I completely rewrote my module just using his code as an example.
Update
Here is my LDAPList module and another support module to convert user access bit codes into something a tiny bit more english-like:
LDAPList.py
# LDAPList.py
# Todd Fiske
# class to encapsulate accessing LDAP information
# 2009-03-18 first version
# 2010-01-04 updated for Python 3 (print functions, <> to !=)
import win32com.client
import UACCodes
ADS_SCOPE_SUBTREE = 2
class LDAPList():
def __init__(self, sContext):
self.Context = sContext # naming context, "DC=xyz,DC=org"
self.objectCategory = ""
self.objectClass = ""
self.FilterClause = ""
self.query = ""
self.cn = None
self.cm = None
self.rs = None
def SetCategory(self, sCategory):
self.objectCategory = sCategory
self.FilterClause = "where objectCategory = '%s'" % self.objectCategory
def SetClass(self, sClass):
self.objectClass = sClass
self.FilterClause = "where objectClass = '%s'" % self.objectClass
def open(self):
self.query = "select * from 'LDAP://%s' %s order by displayName" % (self.Context, self.FilterClause)
self.cn = win32com.client.Dispatch("ADODB.Connection")
self.cm = win32com.client.Dispatch("ADODB.Command")
self.cn.Open("Provider=ADsDSOObject")
self.cm.ActiveConnection = self.cn
self.cm.Properties["Page Size"] = 1000
self.cm.Properties["Searchscope"] = ADS_SCOPE_SUBTREE
self.cm.CommandText = self.query
self.rs = self.cm.Execute()[0]
def close(self):
if self.rs is not None:
self.rs.Close()
self.rs = None
if self.cm is not None:
self.cm = None
if self.cn is not None:
self.cn.Close()
self.cn = None
def count(self):
if self.rs is None:
return -2
return self.rs.RecordCount
def more(self):
if self.rs is None:
return False
return not self.rs.EOF
def GetObject(self):
if self.rs is None:
return None
return win32com.client.GetObject(self.rs.Fields["ADsPath"].Value)
def next(self):
if self.rs is None:
return
self.rs.MoveNext()
#----------
# helper functions
def NamingContext():
# return default naming context
root = win32com.client.GetObject("LDAP://RootDse")
return root.get("DefaultNamingContext")
def AccountControl(obj):
if obj.userAccountControl is not None:
return obj.userAccountControl
else:
return 0
def ConvertUAC(nUAC):
return UACCodes.ConvertUAC(nUAC)
def AccountActive(n):
return (n & UACCodes.ADS_UF_ACCOUNTDISABLE) != UACCodes.ADS_UF_ACCOUNTDISABLE
def GetCategory(obj):
# CN=Group,CN=Schema,CN=Configuration,DC=xyz,DC=org
s = obj.objectCategory
s = s.split(",")[0][3:]
return s
# s = "Group"
def GetGroups(obj):
"""
('CN=XYZ Staff Rockville,OU=Distribution Groups,DC=xyz,DC=org',
'CN=XYZ Staff,OU=Distribution Groups,DC=xyz,DC=org')
"""
if obj.memberOf is None:
return ""
if type(obj.memberOf)==type(()):
tGroups = obj.memberOf
else:
tGroups = (obj.memberOf,)
return tGroups
def GetNameParts(obj):
if obj.givenName is None:
sFirst = ""
else:
sFirst = obj.givenName
if obj.middleName is None:
sMiddle = ""
else:
sMiddle = obj.middleName
if obj.sn is None:
sLast = ""
else:
sLast = obj.sn
if sLast == "" and sFirst == "":
if obj.name is not None:
sName = obj.name
sName = sName[3:]
lParts = sName.split(" ")
if len(lParts) == 1:
"todo: split on embedded capital letter"
print("single-part name: %s" % sName)
sFirst = sName
else:
sLast = lParts[-1]
sFirst = " ".join(lParts[:-1])
return (sFirst, sMiddle, sLast)
def GetManager(obj):
if obj.manager is None:
return ""
else:
return obj.manager
#----------
# test
if __name__ == "__main__":
print
print("testing LDAPList class")
nc = NamingContext()
print("context =", nc)
ll = LDAPList(nc)
ll.SetCategory('user')
ll.open() # generates recordset
print("query = %s" % ll.query)
print("%d items" % ll.count())
n = 0
while (n < 10) and (ll.more()):
o = ll.GetObject() # return
nUAC = AccountControl(o)
print("%-30s %-30s %-30s %-40s %s" % (
o.displayName,
o.name,
o.sAMAccountName,
UACCodes.ConvertUAC(nUAC),
GetManager(o)
))
n += 1
ll.next()
ll.close()
###
UACCodes.py
# UACCodes.py
# Todd Fiske
# generated 2009-09-23 16:36:56 by BuildUACCodes.py
# updated 2010-01-04 for Python 3 (print functions)
# provide UAC constants, lookup list, and conversion function
import sys
# UAC Constants
ADS_UF_SCRIPT = 0x00000001
ADS_UF_ACCOUNTDISABLE = 0x00000002
ADS_UF_HOMEDIR_REQUIRED = 0x00000008
ADS_UF_LOCKOUT = 0x00000010
ADS_UF_PASSWD_NOTREQD = 0x00000020
ADS_UF_PASSWD_CANT_CHANGE = 0x00000040
ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x00000080
ADS_UF_TEMP_DUPLICATE_ACCOUNT = 0x00000100
ADS_UF_NORMAL_ACCOUNT = 0x00000200
ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 0x00000800
ADS_UF_WORKSTATION_TRUST_ACCOUNT = 0x00001000
ADS_UF_SERVER_TRUST_ACCOUNT = 0x00002000
ADS_UF_DONT_EXPIRE_PASSWD = 0x00010000
ADS_UF_MNS_LOGON_ACCOUNT = 0x00020000
ADS_UF_SMARTCARD_REQUIRED = 0x00040000
ADS_UF_TRUSTED_FOR_DELEGATION = 0x00080000
ADS_UF_NOT_DELEGATED = 0x00100000
ADS_UF_USE_DES_KEY_ONLY = 0x00200000
ADS_UF_DONT_REQUIRE_PREAUTH = 0x00400000
ADS_UF_PASSWORD_EXPIRED = 0x00800000
ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x01000000
# UAC short name lookup list
lUACCodes = [
("ADS_UF_SCRIPT" , 0x00000001, "script"),
("ADS_UF_ACCOUNTDISABLE" , 0x00000002, "disabled"),
("ADS_UF_HOMEDIR_REQUIRED" , 0x00000008, "homedir"),
("ADS_UF_LOCKOUT" , 0x00000010, "lockout"),
("ADS_UF_PASSWD_NOTREQD" , 0x00000020, "pwnotreqd"),
("ADS_UF_PASSWD_CANT_CHANGE" , 0x00000040, "pwcantchange"),
("ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED" , 0x00000080, "encryptedpw"),
("ADS_UF_TEMP_DUPLICATE_ACCOUNT" , 0x00000100, "dupaccount"),
("ADS_UF_NORMAL_ACCOUNT" , 0x00000200, "useracct"),
("ADS_UF_INTERDOMAIN_TRUST_ACCOUNT" , 0x00000800, "interdomain"),
("ADS_UF_WORKSTATION_TRUST_ACCOUNT" , 0x00001000, "workstation"),
("ADS_UF_SERVER_TRUST_ACCOUNT" , 0x00002000, "server"),
("ADS_UF_DONT_EXPIRE_PASSWD" , 0x00010000, "pwnoexpire"),
("ADS_UF_MNS_LOGON_ACCOUNT" , 0x00020000, "mnslogon"),
("ADS_UF_SMARTCARD_REQUIRED" , 0x00040000, "smartcard"),
("ADS_UF_TRUSTED_FOR_DELEGATION" , 0x00080000, "trustdeleg"),
("ADS_UF_NOT_DELEGATED" , 0x00100000, "notdeleg"),
("ADS_UF_USE_DES_KEY_ONLY" , 0x00200000, "deskey"),
("ADS_UF_DONT_REQUIRE_PREAUTH" , 0x00400000, "nopreauth"),
("ADS_UF_PASSWORD_EXPIRED" , 0x00800000, "pwexpired"),
("ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION", 0x01000000, "trustauth"),
]
# UAC conversion function
def ConvertUAC(nUAC):
s = ""
for c in lUACCodes:
if ((nUAC & c[1]) == c[1]):
s = s + c[2] + " "
return s
# test routine
if __name__ == "__main__":
print("UACCodes Test")
print("-------------")
for n in [0, 512, 514, 65535]:
print("%d = %s" % (n, ConvertUAC(n)))
print
for s in sys.argv[1:]:
n = int(s)
print("%d = %s" % (n, ConvertUAC(n)))
###
Both modules have some usage examples and should be fairly easy to figure out, but let me know if you have any questions or comments.

There is a Pure Python implementation of an LDAP client called Ldaptor. I don't think it's maintained though. If you really need it, you might be able to run 2to3 on this and port it.

This answer is no longer accurate; see below for other answers.
Sorry to break this on you, but I don't think there is a python-ldap for Python 3 (yet)...
That's the reason why we should keep active development at Python 2.6 for now (as long as most crucial dependencies (libs) are not ported to 3.0).

Related

‘module’ object has no attribute ‘MStatus' in Maya python Api 2020

I am trying to build a command in python for Maya following a course on youtube and it's showing this error "# Error: RuntimeError: file line 2: AttributeError: file C:/Users/saeed/OneDrive/Documents/maya/2020/Plugins/vertexParticle.py line 23: 'module' object has no attribute 'MStatus' # "
I checked Maya API documents and we should have "MStatus" class but I have no idea why it's not accepted, I tried to use "MStatusCode" and it's showing same error.
from maya import OpenMaya
from maya import OpenMayaMPx
from maya import OpenMayaFX
import sys
commandName = "vertexParticle"
kHelpFlag = "-h"
kHelpLongFlag = "-help"
kSparseFlag = "-s"
kSparseLongFlag = "-sparse"
helpMessage = "This command will attach a particle for each vertex"
class pluginCommand(OpenMayaMPx.MPxCommand):
sparse = None
def __init__(self):
OpenMayaMPx.MPxCommand.__init__(self)
def argumentParser(self, argList):
syntax = self.syntax()
parserArguments = OpenMaya.MArgDatabase(syntax, argList)
if parserArguments.isFlagSet(kSparseFlag):
self.sparse = parserArguments.flagArgumentDouble(kSparseFlag, 0)
return OpenMaya.MStatus.kSuccess
if parserArguments.isFlagSet(kSparseLongFlag):
self.sparse = parserArguments.flagArgumentDouble(kSparseLongFlag, 0)
return OpenMaya.MStatus.kSuccess
if parserArguments.isFlagSet(kHelpFlag):
self.setResult(helpMessage)
return OpenMaya.MStatus.kSuccess
if parserArguments.isFlagSet(kHelpLongFlag):
self.setResult(helpMessage)
return OpenMaya.MStatus.kSuccess
def isUndoable(self):
return True
def undoIt(self):
print "undo"
mFnDagNode = OpenMaya.MFnDagNode(self.mObjParticle)
mDagMod = OpenMaya.MDagModifier()
mDagMod.deleteNode(mFnDagNode.parent(0))
mDagMod.doIt()
return OpenMaya.MStatus.kSuccess
def redoIt(self):
mSel = OpenMaya.MSelectionList()
mDagPath = OpenMaya.MDagPath()
mFnMesh = OpenMaya.MFnMesh()
OpenMaya.MGlobal.getActiveSelectionList(mSel)
if mSel.length() >= 1:
try:
mSel.getDagPath(0, mDagPath)
mFnMesh.setObject(mDagPath)
except:
print "please select a poly mesh"
return OpenMaya.MStatus.kUnknownParameter
else:
print "please select a poly mesh"
return OpenMaya.MStatus.kUnknownParameter
mPointArray = OpenMaya.MPointArray()
mFnMesh.getPoints(mPointArray, OpenMaya.MSpace.kWorld)
mFnParticle = OpenMayaFX.MFnParticleSystem()
self.mObjParticle = mFnParticle.create()
mFnParticle = OpenMayaFX.MFnParticleSystem(self.mObjParticle)
counter == 0
for i in xrange(mPointArray.length()):
if i%self.sparse == 0:
mFnParticle.emit(mPointArray[i])
counter += 1
print "total points :" + str(counter)
mFnParticle.saveInitialState()
return OpenMaya.MStatus.kSuccess
def doIt(self, argList):
self.argumentParser(argList)
if self.sparse != None:
self.redoIt()
return OpenMaya.MStatus.kSuccess
def cmdCreator():
return OpenMayaMPx.asMPxPtr(pluginCommand())
def syntaxCreator():
syntax = OpenMaya.MSyntax()
syntax.addFlag(kHelpFlag, kHelpLongFlag)
syntax.addFlag(kSparseFlag, kSparseLongFlag, OpenMaya.MSyntax.kDouble)
return syntax
def initializePlugin(mObject):
mPlugin = OpenMayaMPx.MFnPlugin(mObject)
try:
mPlugin.registerCommand(commandName, cmdCreator, syntaxCreator)
except:
sys.stderr.write("Failed to register" + commandName)
def uninitializePlugin(mObject):
mPlugin = OpenMayaMPx.MFnPlugin(pluginCommand())
try:
mPlugin.deregisterCommand(commandName)
except:
sys.stderr.write("Failed to deregister" + commandName)
Because MStatus.MStatusCode is an enum, you can return its integer value in Python. The only issue is that because in C++ this is an enum, there is no guarantee that the values won't change/shift between releases. This enum has remained consistent since 2019, so there isn't much risk of this happening for MStatus.MStatusCode.
https://help.autodesk.com/view/MAYAUL/2019/ENU/?guid=__cpp_ref_class_m_status_html
https://help.autodesk.com/view/MAYAUL/2023/ENU/?guid=Maya_SDK_cpp_ref_class_m_status_html
Somewhere in the top of your file simply add the constants you intend to use:
MStatus_kSuccess = 0
MStatus_kFailure = 1
MStatus_kUnknownParameter = 5
Then return the constant instead in your functions:
if parserArguments.isFlagSet(kHelpFlag):
self.setResult(helpMessage)
return MStatus_kSuccess

How to send and receive messages between processes in Python (through channels)?

I am trying to implement a simple program in which there are several processes that concurrently communicate with each other by sending and receiving messages. In the program, there are 4 participants (each of which corresponds to a process) and communicate with each other as follows:
P1 sends P2 some_message then P2 sends P3 another_message then P3 sends P4 a_message. Based on the messages each participant receives, they perform a specific action.
Obviously, when, for instance, P1 sends P2 a message, P2 is receiving that message from P1, so they are paired.
I have found different approaches none of which are suitable as they seem to be complicated for I am looking for. For example,
Python MPI which has a restriction of "There are not enough slots available in the system". There are a few ways suggested to sort out the issue but the solutions are a bit complicated.
Socket programming which mostly suits server and client scenario. But my program doesn't have a server. I also checked this answer, which is again based on socket programming.
My question is that isn't there any simpler approach than the above ones so that I can implement what I explained? Is it possible to create communication channels in Python fairly similar to the ones in Golang?
This code I wrote a while ago to get to grips with os.pipe - it is self contained but not "minimally reproducible" since I don't have the time to redo it. It uses tkinter Uis to simulate processes and sends and receives data between them. Note that the code was written only for my private purpose.
"""Test run of the use of pipes between processes.
.. processes are control, startup , send and receive.
.. pipes from control to startup and send
.. pipe from startup to send
.. pipe from send to receive
.. startup, user input of run mode
... prompt, timer (seconds) or number of runs
.. send, user input of data
.. receive, display of data received
. each process operates independently of, and in isolation from, the other processes until data is transferred through pipes
"""
# fr read file descriptor
# fw write file descriptor
# wbs write bytes
# snb string length of output filled with 0 to write as header
# bsnb for number of bytes written, needed for read
# maxbuf number of bytes of header, 4 digits, max 9999 characters in a string/byte literal
# onb output number of bytes
# dbs data read in bytes
import tkinter as tk
from os import pipe as ospipe
from os import read as osread
from os import write as oswrite
from os import close as osclose
from datetime import datetime as dt
from time import monotonic as clock
from functools import partial
BG = '#fa4'
TBG = '#fe8'
SndBG = '#f91'
BLK = '#000'
STOP = '#d30'
START = '#0b0'
start = clock()
def timer(halt):
tm = int(clock())
if int(tm - start) > halt:
return True
else: return False
def piperead(r):
maxbuf = 4
onb = osread(r,maxbuf)
oi = int(onb.decode())
dbs = osread(r,oi).decode() # bytes to string
osclose(r)
return dbs
def pipewrite(w,s):
wbs = bytes(s, encoding='utf-8')
snb = str(len(s)).zfill(4)
bsnb = bytes(snb, encoding='utf-8')
wbs = bsnb + wbs
oswrite(w,wbs)
osclose(w)
def setpipe(process, sub=None, vars=None):
fdr, fdw = ospipe()
if sub: process(fdw,proc=(sub,vars))
else: process(fdw)
return piperead(fdr)
class Sloop():
def __init__(sl, pipewrite=None):
sl.fw = pipewrite
sl.w = tk.Tk()
sl.w.geometry('400x200-100+80')
sl.w.overrideredirect(1)
sl.w['bg'] = BG
uifnt = sl.w.tk.call('font', 'create', 'uifnt', '-family','Consolas', '-size',11)
sl.lvb = tk.Button(sl.w, bg=BLK, activebackground=BG, relief='flat', command=sl.stop)
sl.lvb.pack()
sl.lvb.place(width=15,height=15, x=380,y=10)
sl.sndb = tk.Button(sl.w,bg=SndBG,activebackground=BG,fg=TBG, text=chr(11166), command=sl.send)
sl.sndb.pack()
sl.sndb.place(width=25,height=25, x=20,y=160)
sl.tlbl = tk.Label(sl.w,bg=BG, text='write data to send...')
sl.tlbl.pack()
sl.tlbl.place(x=20,y=20)
sl.t = tk.Text(sl.w,bg=TBG)
sl.t.pack()
sl.t.place(width=300,height=100, x=20,y=45)
sl.t.focus_set()
sl.w.mainloop()
def send(sl):
sl.output = sl.t.get('1.0','end')
if sl.output != '\n':
pipewrite(sl.fw,sl.output)
sl.close()
else:
sl.error()
def error(sl):
def _clearlbl(ev):
sl.erlbl.destroy()
sl.erlbl = tk.Label(sl.w,bg=TBG,text='there is nothing to send')
sl.erlbl.pack()
sl.erlbl.place(x=20,y=160)
sl.t.focus_set()
sl.t.bind('<KeyPress>',_clearlbl)
def stop(sl):
pipewrite(sl.fw,'stop')
sl.close()
def close(sl):
sl.w.destroy()
class Rloop():
def __init__(rl, pipefread=None):
rl.fr = pipefread
rl.w = tk.Tk()
rl.w.geometry('400x200-100+320')
rl.w.overrideredirect(1)
rl.w['bg'] = BG
uifnt = rl.w.tk.call('font', 'create', 'uifnt', '-family','Consolas', '-size',10)
rl.lvb = tk.Button(rl.w, bg=BLK, activebackground=BG, relief='flat', command=rl.close)
rl.lvb.pack()
rl.lvb.place(width=15,height=15, x=380,y=10)
rl.tlbl = tk.Label(rl.w,bg=BG, text='received...')
rl.tlbl.pack()
rl.tlbl.place(x=20,y=20)
rl.t = tk.Text(rl.w,bg=TBG)
rl.t['font'] = uifnt
rl.t.pack()
rl.t.place(width=300,height=100, x=20,y=45)
rl.t.focus_set()
rl.receive()
rl.w.mainloop()
def receive(rl):
rec = piperead(rl.fr)
if rec != 'stop':
rl.t.insert('end', '\n'.join([str(dt.now()), rec]))
else: rl.close()
def close(rl):
rl.w.destroy()
class Startup():
def __init__(su, pipefwrite=None):
su.fw = pipefwrite
su.mode = ''
su.w = tk.Tk()
su.w.geometry('400x200-100+500')
su.w.overrideredirect(1)
su.w['bg'] = BG
uifnt = su.w.tk.call('font', 'create', 'uifnt', '-family','Consolas', '-size',11)
su.lvb = tk.Button(su.w, bg=BLK, activebackground=BG, relief='flat', command=su.stop)
su.lvb.pack()
su.lvb.place(width=15,height=15, x=380,y=10)
su.sndb = tk.Button(su.w,bg=SndBG,activebackground=BG,fg=TBG, text=chr(11166), command=su.send)
su.sndb.pack()
su.sndb.place(width=25,height=25, x=20,y=160)
su.title = tk.Label(su.w,bg=BG, text='Modes to continue data input')
su.title.pack()
su.titley = 10
su.title.place(x=20,y=su.titley)
su.ysp = 20
su.margin = 200
ptxt = 'prompt'
su.pb = tk.Button(su.w,bg=BG, activebackground=BG, text=ptxt, relief='flat', cursor='hand2', command=partial(su._get,e=None, nm='su.pb', ent=None))
tmtxt = ' timer '
su.tmb = tk.Button(su.w,bg=BG, activebackground=BG, text=tmtxt, relief='flat', cursor='hand2', command=partial(su._enter,ent='su.tmb'))
rntxt = ' runs '
su.rnb = tk.Button(su.w,bg=BG, activebackground=BG, text=rntxt, relief='flat', cursor='hand2', command=partial(su._enter,ent='su.rnb'))
su.pb.pack()
su.pby = su.titley + 1.5*su.ysp
su.pb.place(x=25,y=su.pby)
su.tmb.pack()
su.tmby = su.pby + 2*su.ysp
su.tmb.place(x=25,y=su.tmby)
su.rnb.pack()
su.rnby = su.pby + 4*su.ysp
su.rnb.place(x=25,y=su.rnby)
su.formd = {'su.pb':su.pb, 'su.tmb':su.tmb, 'su.rnb':su.rnb}
su.w.mainloop()
def _able(su,nm):
for key in su.formd:
if nm[0:4] not in key:
su.formd[key]['state'] = 'disabled'
else:
su.formd[key]['state'] = 'normal'
def _enter(su,ent):
if ent == 'su.tmb':
tmtxt = 'seconds'
su.tmlbl = tk.Label(su.w,bg=BG, text=tmtxt)
su.tment = tk.Entry(su.w,bg=TBG)
su.tmlbl.pack()
su.tment.pack()
tmlbly = su.tmby
su.tmlbl.place(x=su._margin(tmtxt), y=tmlbly)
su.tment.place(x=su.margin, y=tmlbly)
su.tment.focus_set()
su.tment.bind('<Return>', partial(su._get,nm='su.tment', ent=su.tment))
su.formd = su.formd | {'su.tmlbl':su.tmlbl, 'su.tment':su.tment}
elif ent == 'su.rnb':
rntxt = 'number'
su.rnlbl = tk.Label(su.w,bg=BG, text=rntxt)
su.rnent = tk.Entry(su.w,bg=TBG)
su.rnlbl.pack()
su.rnent.pack()
rnlbly = su.rnby
su.rnlbl.place(x=su._margin(rntxt), y=rnlbly)
su.rnent.place(x=su.margin, y=rnlbly)
su.rnent.focus_set()
su.rnent.bind('<Return>', partial(su._get,nm='su.rnent', ent=su.rnent))
su.formd = su.formd | {'su.rnlbl':su.rnlbl, 'su.rnent':su.rnent}
def _get(su,e,nm,ent):
if nm == 'su.pb':
su._able('su.pb')
su.mode = 'prompt,'+'1'
else:
su._able(nm)
for key in su.formd:
if key == nm:
if 'tm' in key: modestr = 'timer'
elif 'rn' in key: modestr = 'runs'
su.formd[key]['bg']=BG
su.mode = ','.join([modestr,str(ent.get())])
break
def _margin(su,txt):
return su.margin-(len(txt)*8)
def send(su):
pipewrite(su.fw,su.mode)
su.close()
def stop(su):
pipewrite(su.fw,'stop')
su.close()
def close(su):
su.w.destroy()
class Control():
def __init__(c, pipefwrite=None, proc=None):
c.fw = pipefwrite
c.proc = proc
if c.proc:
c.proc = proc[0]
if proc[1]:
c.procv = proc[1]
else:
c.procvl = None
c.procd = {'start':c._strtui, 'prompt':c._prui, 'timer':c._tmui, 'runs':c._rnui}
c.w = tk.Tk()
c.w.geometry('100x200-60+80')
c.w.overrideredirect(1)
c.w['bg'] = BG
uifnt = c.w.tk.call('font', 'create', 'uifnt', '-family','Consolas', '-size',11)
c.lvb = tk.Button(c.w, bg=BLK, activebackground=BG, relief='flat', command=c.stop)
c.lvb.pack()
c.lvb.place(width=15,height=15, x=80,y=10)
c.title = tk.Label(c.w,bg=BG, text='pipe test\nControl')
c.title.pack()
c.title.place(x=5,y=5)
c.stpclr = tk.Label(c.w,bg=STOP)
c.stpclr.pack()
stpy = 160
c.stpclr.place(width=7,height=7,x=2,y=stpy+10)
c.stopb = tk.Button(c.w, bg=BG, text='stop', cursor='hand2', relief='flat', activebackground=BG, command=c.stop)
c.stopb.pack()
c.stopb.place(x=10,y=160)
c.procd[c.proc]()
c.w.mainloop()
def _strtui(c):
c.strtclr = tk.Label(c.w,bg=START)
c.strtclr.pack()
strty = 60
c.strtclr.place(width=7,height=7,x=2,y=strty+10)
c.startb = tk.Button(c.w, bg=BG, text='start', cursor='hand2', relief='flat', activebackground=BG, command=c.strtup)
c.startb.pack()
c.startb.place(x=10,y=strty)
def __write(c,s):
pipewrite(c.fw,s)
c.close()
def _prui(c):
prb = tk.Button(c.w,bg=TBG, text='--- next ---', activebackground=BG, relief='flat',cursor='hand2', command=partial(c.__write,'prompt'))
prb.pack()
prb.place(x=10,y=80)
def __confirm(c):
cb = tk.Button(c.w, bg=TBG, text='confirm', activebackground=BG, relief= 'flat', cursor='hand2', command=partial(c.__write,'confirm'))
cb.pack()
cb.place(x=20,y=120)
def _tmui(c):
tmt = ''.join(['run for\n',str(c.procv),' seconds'])
tmlbl = tk.Label(c.w,bg=BG, text=tmt)
tmlbl.pack()
tmlbl.place(x=10,y=80)
c.__confirm()
def _rnui(c):
rnt = ''.join(['run\n ',str(c.procv),' times'])
rnlbl = tk.Label(c.w,bg=BG, text=rnt)
rnlbl.pack()
rnlbl.place(x=10,y=80)
c.__confirm()
def strtup(c):
pipewrite(c.fw,'startup')
c.close()
def stop(c):
pipewrite(c.fw,'stop')
c.close()
def close(c):
c.w.destroy()
def once():
fr, fw = ospipe()
Sloop(fw)
Rloop(fr)
def many(mkey,mint=1):
"""modes are ('prompt',1), ('timer',secs), ('runs',runs)
"""
if mkey == 'timer':
rec = setpipe(Control,sub='timer',vars=mint)
if rec == 'confirm':
while not timer(mint):
once()
return True
elif rec == 'stop':
return False
elif mkey == 'runs':
rec = setpipe(Control,sub='runs',vars=mint)
if rec == 'confirm':
for r in range(mint):
once()
return True
elif rec == 'stop':
return False
elif mkey == 'prompt':
quit = False
while not quit:
once()
rec = setpipe(Control,sub='prompt')
if rec != 'prompt':
quit = True
return True
def testui():
incontrol = True
while incontrol:
rec = setpipe(Control,sub='start')
if rec == 'startup':
rec = setpipe(Startup)
if rec != 'stop':
modes, p, ns = rec.partition(',')
incontrol = many(modes,int(ns))
else:
incontrol = False
if __name__ == '__main__':
testui()

python myhdl package how to generate verilog initial block

From the code mostly from the sample of myhdl:
from myhdl import Signal, intbv, delay, always, now, Simulation, toVerilog
__debug = True
def ClkDriver(clk):
halfPeriod = delay(10)
#always(halfPeriod)
def driveClk():
clk.next = not clk
return driveClk
def HelloWorld(clk, outs):
counts = intbv(3)[32:]
#always(clk.posedge)
def sayHello():
outs.next = not outs
if counts >= 3 - 1:
counts.next = 0
else:
counts.next = counts + 1
if __debug__:
print "%s Hello World! outs %s %s" % (
now(), str(outs), str(outs.next))
return sayHello
clk = Signal(bool(0))
outs = Signal(intbv(0)[1:])
clkdriver_inst = ClkDriver(clk)
hello_inst = toVerilog(HelloWorld, clk, outs)
sim = Simulation(clkdriver_inst, hello_inst)
sim.run(150)
I expect it to generate a verilog program that contains an initial block, like something:
module HelloWorld(...)
reg [31:0] counts;
initial begin
counts = 32'h3
end
always #(...
How can you get the initial block generated?
Note that on the google cache for old.myhdl.org/doku.php/dev:initial_values it links to example https://bitbucket.org/cfelton/examples/src/tip/ramrom/ . So it looks the feature should be supported. However the rom sample generates static case statements. That's not what I'm looking for.
Three steps to resolve it:
Update to the latest myhdl on master or a version that contains the hash 87784ad which added the feature under issue #105 or #150. As an example for virtualenv, run a git clone, followed by pip install -e <path-to-myhdl-dir>.
Change the signal to a list.
Set toVerilog.initial_values=True before calling toVerilog.
Code snippet follows.
def HelloWorld(clk, outs):
counts = [Signal(intbv(3)[32:])]
#always(clk.posedge)
def sayHello():
outs.next = not outs
if counts[0] >= 3 - 1:
counts[0].next = 0
else:
counts[0].next = counts[0] + 1
if __debug__:
print "%s Hello World! outs %s %s %d" % (
now(), str(outs), str(outs.next), counts[0])
return sayHello
clk = Signal(bool(0))
outs = Signal(intbv(0)[1:])
clkdriver_inst = ClkDriver(clk)
toVerilog.initial_values=True
hello_inst = toVerilog(HelloWorld, clk, outs)
sim = Simulation(clkdriver_inst, hello_inst)
sim.run(150)

Run python script to replace betacode with greek letters LaTeX

I want to convert the betacode in an existing .tex-File to normal greek letters.
For example: I want to replace:
\bcode{lo/gos}
with simple:
λόγος
And so on for all other glyphs. Fortunately there seems to be a python-script that is supposed to do just that. But, being completely inexperienced I simply don’t know how to run it.
Here is the code of the python sript:
# beta2unicode.py
#
# Version 2004-11-23
#
# James Tauber
# http://jtauber.com/
#
# You are free to redistribute this, but please inform me of any errors
#
# USAGE:
#
# trie = beta2unicodeTrie()
# beta = "LO/GOS\n";
# unicode, remainder = trie.convert(beta)
#
# - to get final sigma, string must end in \n
# - remainder will contain rest of beta if not all can be converted
class Trie:
def __init__(self):
self.root = [None, {}]
def add(self, key, value):
curr_node = self.root
for ch in key:
curr_node = curr_node[1].setdefault(ch, [None, {}])
curr_node[0] = value
def find(self, key):
curr_node = self.root
for ch in key:
try:
curr_node = curr_node[1][ch]
except KeyError:
return None
return curr_node[0]
def findp(self, key):
curr_node = self.root
remainder = key
for ch in key:
try:
curr_node = curr_node[1][ch]
except KeyError:
return (curr_node[0], remainder)
remainder = remainder[1:]
return (curr_node[0], remainder)
def convert(self, keystring):
valuestring = ""
key = keystring
while key:
value, key = self.findp(key)
if not value:
return (valuestring, key)
valuestring += value
return (valuestring, key)
def beta2unicodeTrie():
t = Trie()
t.add("*A", u"\u0391")
t.add("*B", u"\u0392")
t.add("*G", u"\u0393")
t.add("*D", u"\u0394")
t.add("*E", u"\u0395")
t.add("*Z", u"\u0396")
t.add("*H", u"\u0397")
t.add("*Q", u"\u0398")
t.add("*I", u"\u0399")
t.add("*K", u"\u039A")
t.add("*L", u"\u039B")
t.add("*M", u"\u039C")
t.add("*N", u"\u039D")
t.add("*C", u"\u039E")
t.add("*O", u"\u039F")
t.add("*P", u"\u03A0")
t.add("*R", u"\u03A1")
t.add("*S", u"\u03A3")
t.add("*T", u"\u03A4")
t.add("*U", u"\u03A5")
t.add("*F", u"\u03A6")
t.add("*X", u"\u03A7")
t.add("*Y", u"\u03A8")
t.add("*W", u"\u03A9")
t.add("A", u"\u03B1")
t.add("B", u"\u03B2")
t.add("G", u"\u03B3")
t.add("D", u"\u03B4")
t.add("E", u"\u03B5")
t.add("Z", u"\u03B6")
t.add("H", u"\u03B7")
t.add("Q", u"\u03B8")
t.add("I", u"\u03B9")
t.add("K", u"\u03BA")
t.add("L", u"\u03BB")
t.add("M", u"\u03BC")
t.add("N", u"\u03BD")
t.add("C", u"\u03BE")
t.add("O", u"\u03BF")
t.add("P", u"\u03C0")
t.add("R", u"\u03C1")
t.add("S\n", u"\u03C2")
t.add("S,", u"\u03C2,")
t.add("S.", u"\u03C2.")
t.add("S:", u"\u03C2:")
t.add("S;", u"\u03C2;")
t.add("S]", u"\u03C2]")
t.add("S#", u"\u03C2#")
t.add("S_", u"\u03C2_")
t.add("S", u"\u03C3")
t.add("T", u"\u03C4")
t.add("U", u"\u03C5")
t.add("F", u"\u03C6")
t.add("X", u"\u03C7")
t.add("Y", u"\u03C8")
t.add("W", u"\u03C9")
t.add("I+", U"\u03CA")
t.add("U+", U"\u03CB")
t.add("A)", u"\u1F00")
t.add("A(", u"\u1F01")
t.add("A)\\", u"\u1F02")
t.add("A(\\", u"\u1F03")
t.add("A)/", u"\u1F04")
t.add("A(/", u"\u1F05")
t.add("E)", u"\u1F10")
t.add("E(", u"\u1F11")
t.add("E)\\", u"\u1F12")
t.add("E(\\", u"\u1F13")
t.add("E)/", u"\u1F14")
t.add("E(/", u"\u1F15")
t.add("H)", u"\u1F20")
t.add("H(", u"\u1F21")
t.add("H)\\", u"\u1F22")
t.add("H(\\", u"\u1F23")
t.add("H)/", u"\u1F24")
t.add("H(/", u"\u1F25")
t.add("I)", u"\u1F30")
t.add("I(", u"\u1F31")
t.add("I)\\", u"\u1F32")
t.add("I(\\", u"\u1F33")
t.add("I)/", u"\u1F34")
t.add("I(/", u"\u1F35")
t.add("O)", u"\u1F40")
t.add("O(", u"\u1F41")
t.add("O)\\", u"\u1F42")
t.add("O(\\", u"\u1F43")
t.add("O)/", u"\u1F44")
t.add("O(/", u"\u1F45")
t.add("U)", u"\u1F50")
t.add("U(", u"\u1F51")
t.add("U)\\", u"\u1F52")
t.add("U(\\", u"\u1F53")
t.add("U)/", u"\u1F54")
t.add("U(/", u"\u1F55")
t.add("W)", u"\u1F60")
t.add("W(", u"\u1F61")
t.add("W)\\", u"\u1F62")
t.add("W(\\", u"\u1F63")
t.add("W)/", u"\u1F64")
t.add("W(/", u"\u1F65")
t.add("A)=", u"\u1F06")
t.add("A(=", u"\u1F07")
t.add("H)=", u"\u1F26")
t.add("H(=", u"\u1F27")
t.add("I)=", u"\u1F36")
t.add("I(=", u"\u1F37")
t.add("U)=", u"\u1F56")
t.add("U(=", u"\u1F57")
t.add("W)=", u"\u1F66")
t.add("W(=", u"\u1F67")
t.add("*A)", u"\u1F08")
t.add("*)A", u"\u1F08")
t.add("*A(", u"\u1F09")
t.add("*(A", u"\u1F09")
#
t.add("*(\A", u"\u1F0B")
t.add("*A)/", u"\u1F0C")
t.add("*)/A", u"\u1F0C")
t.add("*A(/", u"\u1F0F")
t.add("*(/A", u"\u1F0F")
t.add("*E)", u"\u1F18")
t.add("*)E", u"\u1F18")
t.add("*E(", u"\u1F19")
t.add("*(E", u"\u1F19")
#
t.add("*(\E", u"\u1F1B")
t.add("*E)/", u"\u1F1C")
t.add("*)/E", u"\u1F1C")
t.add("*E(/", u"\u1F1D")
t.add("*(/E", u"\u1F1D")
t.add("*H)", u"\u1F28")
t.add("*)H", u"\u1F28")
t.add("*H(", u"\u1F29")
t.add("*(H", u"\u1F29")
t.add("*H)\\", u"\u1F2A")
t.add(")\\*H", u"\u1F2A")
t.add("*)\\H", u"\u1F2A")
#
t.add("*H)/", u"\u1F2C")
t.add("*)/H", u"\u1F2C")
#
t.add("*)=H", u"\u1F2E")
t.add("(/*H", u"\u1F2F")
t.add("*(/H", u"\u1F2F")
t.add("*I)", u"\u1F38")
t.add("*)I", u"\u1F38")
t.add("*I(", u"\u1F39")
t.add("*(I", u"\u1F39")
#
#
t.add("*I)/", u"\u1F3C")
t.add("*)/I", u"\u1F3C")
#
#
t.add("*I(/", u"\u1F3F")
t.add("*(/I", u"\u1F3F")
#
t.add("*O)", u"\u1F48")
t.add("*)O", u"\u1F48")
t.add("*O(", u"\u1F49")
t.add("*(O", u"\u1F49")
#
#
t.add("*(\O", u"\u1F4B")
t.add("*O)/", u"\u1F4C")
t.add("*)/O", u"\u1F4C")
t.add("*O(/", u"\u1F4F")
t.add("*(/O", u"\u1F4F")
#
t.add("*U(", u"\u1F59")
t.add("*(U", u"\u1F59")
#
t.add("*(/U", u"\u1F5D")
#
t.add("*(=U", u"\u1F5F")
t.add("*W)", u"\u1F68")
t.add("*W(", u"\u1F69")
t.add("*(W", u"\u1F69")
#
#
t.add("*W)/", u"\u1F6C")
t.add("*)/W", u"\u1F6C")
t.add("*W(/", u"\u1F6F")
t.add("*(/W", u"\u1F6F")
t.add("*A)=", u"\u1F0E")
t.add("*)=A", u"\u1F0E")
t.add("*A(=", u"\u1F0F")
t.add("*W)=", u"\u1F6E")
t.add("*)=W", u"\u1F6E")
t.add("*W(=", u"\u1F6F")
t.add("*(=W", u"\u1F6F")
t.add("A\\", u"\u1F70")
t.add("A/", u"\u1F71")
t.add("E\\", u"\u1F72")
t.add("E/", u"\u1F73")
t.add("H\\", u"\u1F74")
t.add("H/", u"\u1F75")
t.add("I\\", u"\u1F76")
t.add("I/", u"\u1F77")
t.add("O\\", u"\u1F78")
t.add("O/", u"\u1F79")
t.add("U\\", u"\u1F7A")
t.add("U/", u"\u1F7B")
t.add("W\\", u"\u1F7C")
t.add("W/", u"\u1F7D")
t.add("A)/|", u"\u1F84")
t.add("A(/|", u"\u1F85")
t.add("H)|", u"\u1F90")
t.add("H(|", u"\u1F91")
t.add("H)/|", u"\u1F94")
t.add("H)=|", u"\u1F96")
t.add("H(=|", u"\u1F97")
t.add("W)|", u"\u1FA0")
t.add("W(=|", u"\u1FA7")
t.add("A=", u"\u1FB6")
t.add("H=", u"\u1FC6")
t.add("I=", u"\u1FD6")
t.add("U=", u"\u1FE6")
t.add("W=", u"\u1FF6")
t.add("I\\+", u"\u1FD2")
t.add("I/+", u"\u1FD3")
t.add("I+/", u"\u1FD3")
t.add("U\\+", u"\u1FE2")
t.add("U/+", u"\u1FE3")
t.add("A|", u"\u1FB3")
t.add("A/|", u"\u1FB4")
t.add("H|", u"\u1FC3")
t.add("H/|", u"\u1FC4")
t.add("W|", u"\u1FF3")
t.add("W|/", u"\u1FF4")
t.add("W/|", u"\u1FF4")
t.add("A=|", u"\u1FB7")
t.add("H=|", u"\u1FC7")
t.add("W=|", u"\u1FF7")
t.add("R(", u"\u1FE4")
t.add("*R(", u"\u1FEC")
t.add("*(R", u"\u1FEC")
# t.add("~", u"~")
# t.add("-", u"-")
# t.add("(null)", u"(null)")
# t.add("&", "&")
t.add("0", u"0")
t.add("1", u"1")
t.add("2", u"2")
t.add("3", u"3")
t.add("4", u"4")
t.add("5", u"5")
t.add("6", u"6")
t.add("7", u"7")
t.add("8", u"8")
t.add("9", u"9")
t.add("#", u"#")
t.add("$", u"$")
t.add(" ", u" ")
t.add(".", u".")
t.add(",", u",")
t.add("'", u"'")
t.add(":", u":")
t.add(";", u";")
t.add("_", u"_")
t.add("[", u"[")
t.add("]", u"]")
t.add("\n", u"")
return t
t = beta2unicodeTrie()
import sys
for line in file(sys.argv[1]):
a, b = t.convert(line)
if b:
print a.encode("utf-8"), b
raise Exception
print a.encode("utf-8")
And here is a little .tex-file with which it should work.
\documentclass[12pt]{scrbook}
\usepackage[polutonikogreek, ngerman]{babel}
\usepackage[ngerman]{betababel}
\usepackage{fontspec}
%\defaultfontfeatures{Ligatures=TeX}
%\newfontfeature{Microtype}{protrusion=default;expansion=default;}
\begin{document}
\bcode{lo/gos}
\end{document}
In case the script does not work: would it be possible to convert all the strings within the \bcode-Makro with something like regex? For example the "o/" to the ό and so on? What would be the weapon of choice here?
Do I have python installed?
Try python -V at a shell prompt. Your code is python 2 code, so you will a python 2 version.
I need to install Python
Most straight forward way if you don't need a complex environment (and you don't for this problem) is just to go to python.org. Don't forget you need python 2.
Running the program
Generally it will be as simple as:
python beta2unicode.py myfile.tex-file
And to capture the output:
python beta2unicode.py myfile.tex-file > myfile.not-tex-file
Does the script work?
Almost. You will need to replace the code at the end of the script that starts the same way this does, with this:
import sys
t = beta2unicodeTrie()
import re
BCODE = re.compile(r'\\bcode{[^}]*}')
for line in open(sys.argv[1]):
matches = BCODE.search(line)
for match in BCODE.findall(line):
bcode = match[7:-1]
a, b = t.convert(bcode.upper())
if b:
raise IOError("failed conversion '%s' in '%s'" % (b, line))
converted = a.encode("utf-8")
line = line.replace(match, converted)
print(line.rstrip())
Results
\documentclass[12pt]{scrbook}
\usepackage[polutonikogreek, ngerman]{babel}
\usepackage[ngerman]{betababel}
\usepackage{fontspec}
%\defaultfontfeatures{Ligatures=TeX}
%\newfontfeature{Microtype}{protrusion=default;expansion=default;}
\begin{document}
λόγοσ
\end{document}

How to specify multiple author(s) / email(s) in setup.py

We wrote a small wrapper to a twitter app and published this information to http://pypi.python.org. But setup.py just contained a single field for specifying email / name of the author. How do I specify multiple contributors / email list, to the following fields since we would like this package to be listed under our names, much similar to how it shows up in http://rubygems.org.
author='foo',
author_email='foo.bar#gmail.com',
As far as I know, setuptools doesn't support using a list of strings in order to specify multiple authors. Your best bet is to list the authors in a single string:
author='Foo Bar, Spam Eggs',
author_email='foobar#baz.com, spameggs#joe.org',
I'm not sure if PyPI validates the author_email field, so you may run into trouble with that one. In any case, I would recommend you limit these to a single author and mention all contributors in the documentation or description.
Some sources:
This has been registered as a bug, actually, but it seems like support for multiple authors was not implemented. Here is an alternative solution. Here is an idea for how to provide a contact email for a project with multiple authors.
I'm sort of just piggybacking off of #modocache's answer, in case you want some specifics.
Throughout this answer, I'll be refering to a python3.6 version of the FOO-PYTHON-ENV\Lib\distutils\dist.py file
To reiterate, you cannot use a list in the author field. Here's why:
Spoiler: Two methods belonging to the DistributionMetadata class are the reason --
def _read_field(name):
value = msg[name]
if value == 'UNKNOWN':
return None
return value
def _read_list(name):
values = msg.get_all(name, None)
if values == []:
return None
return values
Here's where you'll hit an error if you try to stick a list in the author field:
class DistributionMetadata:
#*...(R E D A C T E D)...*#
def read_pkg_file(self, file):
"""Reads the metadata values from a file object."""
#*...(R E D A C T E D)...*#
# ####################################
# Note the usage of _read_field() here
# ####################################
self.name = _read_field('name')
self.version = _read_field('version')
self.description = _read_field('summary')
# we are filling author only.
self.author = _read_field('author')
self.maintainer = None
self.author_email = _read_field('author-email')
self.maintainer_email = None
self.url = _read_field('home-page')
self.license = _read_field('license')
#*...(R E D A C T E D)...*#
# ###################################
# Note the usage of _read_list() here
# ###################################
self.platforms = _read_list('platform')
self.classifiers = _read_list('classifier')
#*...(R E D A C T E D)...*#
& Here's the whole thing:
class DistributionMetadata:
"""Dummy class to hold the distribution meta-data: name, version,
author, and so forth.
"""
_METHOD_BASENAMES = ("name", "version", "author", "author_email",
"maintainer", "maintainer_email", "url",
"license", "description", "long_description",
"keywords", "platforms", "fullname", "contact",
"contact_email", "classifiers", "download_url",
# PEP 314
"provides", "requires", "obsoletes",
)
def __init__(self, path=None):
if path is not None:
self.read_pkg_file(open(path))
else:
self.name = None
self.version = None
self.author = None
self.author_email = None
self.maintainer = None
self.maintainer_email = None
self.url = None
self.license = None
self.description = None
self.long_description = None
self.keywords = None
self.platforms = None
self.classifiers = None
self.download_url = None
# PEP 314
self.provides = None
self.requires = None
self.obsoletes = None
def read_pkg_file(self, file):
"""Reads the metadata values from a file object."""
msg = message_from_file(file)
def _read_field(name):
value = msg[name]
if value == 'UNKNOWN':
return None
return value
def _read_list(name):
values = msg.get_all(name, None)
if values == []:
return None
return values
metadata_version = msg['metadata-version']
self.name = _read_field('name')
self.version = _read_field('version')
self.description = _read_field('summary')
# we are filling author only.
self.author = _read_field('author')
self.maintainer = None
self.author_email = _read_field('author-email')
self.maintainer_email = None
self.url = _read_field('home-page')
self.license = _read_field('license')
if 'download-url' in msg:
self.download_url = _read_field('download-url')
else:
self.download_url = None
self.long_description = _read_field('description')
self.description = _read_field('summary')
if 'keywords' in msg:
self.keywords = _read_field('keywords').split(',')
self.platforms = _read_list('platform')
self.classifiers = _read_list('classifier')
# PEP 314 - these fields only exist in 1.1
if metadata_version == '1.1':
self.requires = _read_list('requires')
self.provides = _read_list('provides')
self.obsoletes = _read_list('obsoletes')
else:
self.requires = None
self.provides = None
self.obsoletes = None
Consider using flit to build the package, as this build system supports multiple authors and maintainers. Store this metadata in pyproject.toml as follows:
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
[project]
...
authors = [
{name = "First1 Last1", email = "name1#foo.bar"},
{name = "First2 Last2", email = "name2#foo.bar"},
]
maintainers = [
{name = "First1 Last1", email = "name1#foo.bar"},
{name = "First2 Last2", email = "name2#foo.bar"},
]

Categories

Resources