The 'cmyui' module on PyPI; just a bunch of tools I find myself reusing constantly (web, sql, discord & osu! utils, etc.)
PythonMIT
Generic multipurpose library for the average cmyui (and alike)
The good stuff
Async multi-domain http server & sql wrapper
Simple logging utilities, for printing in colour w/ timestamps.
osu! tools, such as replay and beatmap parsers, and more.
Simple discord webhook wrapper, likely going to grow into more.
# Example of how to use some of the stuff.importasyncioimporttimeimportrefromtypingimportNoReturnfromtypingimportOptionalfromcmyui.loggingimportAnsifromcmyui.loggingimportlogfromcmyui.loggingimportRainbowfromcmyui.loggingimportRGBfromcmyui.mysqlimportAsyncSQLPoolfromcmyui.versionimportVersionfromcmyui.utilsimportrstringfromcmyui.webimportConnectionfromcmyui.webimportDomainfromcmyui.webimportServerfrompathlibimportPathversion=Version(1, 0, 3)
debug=Truesql: Optional[AsyncSQLPool] =Noneplayers= [justimaginethisisalistwithplayerobjectsonagameserver]
# server has built-in gzip compression support,# simply pass the level you'd like to use (1-9).app=Server(name=f'Gameserver v{version}',
gzip=4, verbose=debug)
# usually, domains are defined externally in# other files, generally in a 'domains' folder.domain1=Domain('osu.ppy.sh')
domain2=Domain('cmyui.codes')
# domains can then have their routes defined# in similar syntax to many other popular web# frameworks. these domains can be defined# either with a plaintext url route, or using# regular expressions, allowing for much# greater complexity.@domain1.route('/ingame/getfriends.php')asyncdefingame_getfriends(conn: Connection) ->Optional[bytes]:
if'token'notinconn.headers:
# returning a tuple of (int, bytes) allows# for customization of the return code.return (400, b'Bad Request')
token=conn.headers['token']
globalplayersifnottokeninconn.headers:
return (401, b'Unauthorized')
# returning bytes alone will simply use 200.return'\n'.join(players[token].friends).encode()
# methods can be specified as a list in the route definition@domain1.route('/ingame/screenshot.php', methods=['POST'])asyncdefingame_screenshot(conn: Connection) ->Optional[bytes]:
if'token'notinconn.headersor'ss'notinconn.files:
return (400, b'Bad Request')
token=conn.headers['token']
globalplayersifnottokeninconn.headers:
return (401, b'Unauthorized')
p=players[token]
ss_file=Path.cwd() /'ss'/rstring(8)
withopen(ss_file, 'wb') asf:
f.write(conn.files['ss'])
# there are three colour options available,log(f'{p!r} uploaded {ss_file}.', Ansi.LBLUE)
log(f'{p!r} uploaded {ss_file}.', RGB(0x77ffdd))
log(f'{p!r} uploaded {ss_file}.', Rainbow)
returnb'Uploaded'@domain2.route(re.compile('^/u/(?P<id>\d{1,10}$'))asyncdefuser_profile(conn: Connection) ->Optional[bytes]:
... # TODO: templates implementation?# finally, the domains themselves# can be added to the server object.app.add_domains({domain1, domain2})
# and the server allows for any number# of async callables to be enqueued as# tasks once the server is started up.asyncdefon_start():
# this should probably be# in a config somewhere lolsql_info= {
'db': 'cmyui',
'host': 'localhost',
'password': 'lol123',
'user': 'cmyui'
}
globalsqlsql=AsyncSQLPool()
awaitsql.connect(sql_info)
asyncdefdisconnect_inactive_players() ->NoReturn:
ping_timeout=120globalplayerswhileTrue:
forpinplayers:
iftime.time() -p.last_recv_time>ping_timeout:
awaitp.logout()
awaitasyncio.sleep(ping_timeout)
app.add_task(on_start())
app.add_tasks({on_start(), disconnect_inactive_players()})
# both inet & unix sockets are supported.server_addr= ('127.0.0.1', 5001) # inet4server_addr='/tmp/myserver.sock'# unix# then, the server can be run; this is a blocking# call after which the server will indefinitely# continue to listen for and handle connections.app.run(server_addr)
# and voila, you have an async server. the server# will use uvloop if you have it installed; if you# don't know about the project, consider checking# out https://github.com/MagicStack/uvloop.# cheers B)