Skip to content

Instantly share code, notes, and snippets.

@DiamondDemon669
Last active July 7, 2022 17:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DiamondDemon669/1cf245fcce42c1d4057e1b52058d12f7 to your computer and use it in GitHub Desktop.
Save DiamondDemon669/1cf245fcce42c1d4057e1b52058d12f7 to your computer and use it in GitHub Desktop.
Pyspeed: an asynchronous CLI tool made to speed up python by avoiding running the script running more than once using sockets and/or files
import asyncio, socket, os, shlex
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from watchdog.observers.polling import PollingObserver
from multiprocessing import Process
class socket_handler:
def __init__(self):
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
async def run_server(self, address, port, printrun, handle):
server = self.server
server.bind((address, port))
server.listen(8)
if printrun:
print("Running!")
server.setblocking(False)
loop = asyncio.get_event_loop()
while True:
client, _ = await loop.sock_accept(server)
loop.create_task(handle(client))
async def handle_client(self, client):
loop = asyncio.get_event_loop()
request = None
try:
while True:
request = (await loop.sock_recv(client, 255)).decode('utf8')
response = self.handle_request(client, *([''] + shlex.split(str(request).replace('\n', '')))) + '\n'
await loop.sock_sendall(client, response.encode('utf8'))
client.close()
except (BrokenPipeError, ConnectionAbortedError):
pass
def handle_request(self, client, *argv):
return ''.join(argv)
def run(self, address="localhost", port=50200, printrun=False):
try:
asyncio.run(self.run_server(address, port, printrun, self.handle_client))
except KeyboardInterrupt:
self.server.shutdown(socket.SHUT_RDWR)
self.server.close()
class file_handler(FileSystemEventHandler):
def __init__(self, handle_path=''):
self.observer = Observer()
self.handle_path = handle_path
self.modified = False
if isinstance(self.observer, PollingObserver):
raise TypeError("Cannot use polling observer")
def run(self, handle_path=None, printrun=False):
if not handle_path:
handle_path = self.handle_path
self.handle_path = handle_path
self.observer.schedule(self, path=handle_path, recursive=False)
self.observer.start()
if printrun:
print("Running!")
try:
while True:
pass
except KeyboardInterrupt:
self.observer.stop()
self.observer.join()
def clear_handle(self):
with open(self.handle_path, "w+") as file:
self.modified = True
file.write('')
return True
def generate_handle(self, program_name, handle_path=None):
if not handle_path:
handle_path = self.handle_path
with open(os.path.join(handle_path, program_name), 'r+') as handlefile:
self.handle_path = os.path.join(handle_path, program_name)
return handlefile
def on_modified(self, event):
if os.path.isdir(event.src_path): return
if not self.modified:
with open(event.src_path, 'r+') as file:
lines = file.readlines().copy()
file.seek(0)
for x in lines:
file.write(self.handle_data(file, *([''] + shlex.split(x.replace('\n', '')))))
self.modified = True
else:
self.modified = False
def handle_data(self, file, *argv):
return ''.join(argv)
class pyspeed_handler(file_handler, socket_handler):
def __init__(self, handle_path=''):
socket_handler.__init__(self)
file_handler.__init__(self, handle_path)
def run(self, address="localhost", port=50200, handle_path=None, printrun=False):
socketproc = Process(target=socket_handler.run, args=(self, address, port, printrun))
fileproc = Process(target=file_handler.run, args=(self, handle_path, printrun))
socketproc.start()
fileproc.start()
try:
while True:
pass
except KeyboardInterrupt:
socketproc.terminate()
fileproc.terminate()
socketproc.join()
fileproc.join()
def handle_request(self, client, *argv):
return self.handle_all(self, client, *argv)
def handle_data(self, file, *argv):
return self.handle_all(self, file, *argv)
def handle_all(self, ctx, *argv):
return ''.join(argv)
@DiamondDemon669
Copy link
Author

DiamondDemon669 commented Apr 24, 2022

Docs

Usage

Socket handler usage

The socket handler opens a local socket to send data to the script. to parse data, override the handle_request method. the return value will be sent back to the client. all handlers will take a source object and all the data split up by spaces in *argv. for the socket object, the source is the client object. argv[0] is a blank string. to run the server, call the run method with the address and port parameters

import pyspeed

class myhandler(pyspeed.socket_handler):
    def handle_request(self, client, *argv):
        return f"hi, {argv[1]}"

handler = myhandler()
handler.run(address="localhost", port=50200)

To send input to the script, run the script in the background then use this function
however you need to know how long it will take for the code to run
Windows (requires cygwin tools timeout and nc):

@echo off
echo %* | timeout 0.1 nc -w1 127.0.0.1 50200

Linux/MacOS:

sayhi(){ printf "$@" | timeout 0.1 nc 127.0.0.1 50200; }

File handler usage

The file handler watches a file for changes and sends any written data to the handle_data method. the return value will be written to the file and the source object is a file object of the handle file. the argv parameter has the same rules as the socket handler. to run the listener, call the run method with the handle_file parameter

import pyspeed

class myhandler(pyspeed.file_handler):
    def handle_data(self, file, *argv):
        return f"hi, {argv[1]}"

handler = myhandler()
handler.run("/tmp/test")

This class requires watchdog dependency
you still need to know the timing of the code for input/output
Windows:

@echo off
echo %* > C:\TEMP\test
timeout /t 1
type C:\TEMP\test

Linux/MacOS:

sayhi(){ printf "$@" > /tmp/test; sleep 1; cat /tmp/test; }

Combined handler

The combined handler opens a socket and listens for modifications on a file. it sends socket data to handle_request, file data to handle_data and both to handle_all. the source object for handle_request will be a client object, a file object for handle_data and either for handle_all. the return value will be either sent back to the socket or written to the file depending on the method. to run the server, call the run method and pass the address, port and handle_path which will be passed to the handlers.

import pyspeed

class myhandler(pyspeed.pyspeed_handler):
    def handle_all(self, ctx, *argv):
        return f"hi, {argv[1]}"

handler = myhandler()
handler.run(address="localhost", port=50200, handle_path="/tmp/test")

Runs both handlers, so any of the above I/O commands will work

API reference

class socket_handler

async def run_server

Method behind socket_handler.run. Do not run this, use the run method

async def handle_client

Method behind socket_handler.handle_request, listens for connections

def handle_request(self, client, *argv)

Method called upon connection by socket_handler.handle_client. accepts the socket client and a list of arguments, UNIX-like.
the return value will be sent back
Override this method

def run(self, address, port, printrun=False)

call this method to run the server. printrun is purely for debugging purposes

class file_handler(watchdog.events.FileSystemEventHandler)

def run(self, handle_path=None, printrun=False)

Call this method to run the server. handle_path argument is a path to the file the server will listen on and printrun is for debugging purposes. if no handle path is provided, it will use the one provided on initialization

def clear_handle(self)

Do not run this method in handle_data method, or you will be stuck in recursive loop. Clears the handle file

def generate_handle(self, program_name, handle_path=None)

Generates a handle file for you. nothing special.

def on_modified(self, event)

Called when handle file is modified. Calls handle_data method

def handle_data(self, file, *argv)

Called by on_modified method. accepts file object and list of arguments.
the return value will be written to the file
Override this method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment