Package: list2term
list2termThelist2termmodule provides a convenient way to mirror a list to the terminal and helper methods to display messages from concurrentasyncioormultiprocessing Poolprocesses. Thelist2term.Linesclass is a subclass ofcollections.UserListand is tty aware thus it can be safely used in non-tty environments. This class takes a list instance as an argument and when instantiated is accessible via the data attribute. The list can be any iterable, but its elements need to be printable; they should implementstrfunction. The intent of this class is to display relatively small lists to the terminal and dynamically update the terminal when list elements are upated, added or removed. Thus it is able to mirror a List of objects to the terminal.Installationpipinstalllist2termLinesLines(
    data=None,
    size=None,
    lookup=None,
    show_index=True,
    show_x_axis=True,
    max_chars=None,
    use_color=True)DocumentationParametersdata- A list of items to mirror to the terminal.size- An integer designating the size of the initial list - each list item will be initialized to empty string. This parameter should not be used if providing a list of items using thedataparameter.lookup- A list containing unique identifiers that will be used to determine the index of the line to update when using thewritemethod. This parameter should only be used when usinglist2termto print messages from background processes running in a multiprocessing pool. The size of the lookup list should be the same size as thedatalist orsizeif provided.show_index- A boolean to designate if the index for each list item is to be printed, default is True.show_x_axis- A boolean to designate if the X-axis is to be printed, default is False.max_chars- An integer designating the maximum length of any list item, if any item is longer than max_chars then the excess will be cut off and the last three digits will be replaced with '...', default is 150.use_color- A boolean to designate if each list index should be printed with color, default is True.Functionswrite(str)Update appropriate line with the message contained within str. The str must be of the format{identity}->{message}. The index of the line to update is determined by extracting the identity contained within str, then returning the index of the identity from thelookuplist provided to theLinescontructor.Display list -example1Start with a list of 15 items containing random sentences, then update sentences at random indexes. As items in the list are updated the respective line in the terminal is updated to show the current contents of the list.CodeimporttimeimportrandomfromfakerimportFakerfromlist2termimportLinesdefmain():print('Generating random sentences...')docgen=Faker()withLines(size=15,show_x_axis=True,max_chars=100)aslines:for_inrange(200):index=random.randint(0,len(lines)-1)lines[index]=docgen.sentence()time.sleep(.05)if__name__=='__main__':main()Display list of dynamic size -example2Start with a list of 10 items containing random sentences, then add sentences to the list, update existing sentences or remove items from the list at random indexes. As items in the list are added, updated, and removed the respective line in the terminal is updated to show the current contents of the list.CodeimporttimeimportrandomfromfakerimportFakerfromlist2termimportLinesdefmain():print('Generating random sentences...')docgen=Faker()withLines(data=['']*10,max_chars=100)aslines:for_inrange(100):index=random.randint(0,len(lines)-1)lines[index]=docgen.sentence()for_inrange(100):update=['update']*18append=['append']*18pop=['pop']*14clear=['clear']choice=random.choice(append+pop+clear+update)ifchoice=='pop':iflen(lines)>0:index=random.randint(0,len(lines)-1)lines.pop(index)elifchoice=='append':lines.append(docgen.sentence())elifchoice=='update':iflen(lines)>0:index=random.randint(0,len(lines)-1)lines[index]=docgen.sentence()else:iflen(lines)>0:lines.pop()iflen(lines)>0:lines.pop()time.sleep(.1)if__name__=='__main__':main()Display messages fromasyncioprocesses -example3This example demonstrates howlist2termcan be used to display messages from asyncio processes to the terminal. Each item of the list represents a asnycio process.CodeimportasyncioimportrandomfromfakerimportFakerfromlist2termimportLinesasyncdefdo_work(worker,lines):total=random.randint(10,65)for_inrange(total):# mimic an IO-bound processawaitasyncio.sleep(random.choice([.05,.1,.025]))lines[worker]=f'processed{Faker().name()}'returntotalasyncdefrun(workers):withLines(size=workers)aslines:returnawaitasyncio.gather(*(do_work(worker,lines)forworkerinrange(workers)))defmain():workers=15print(f'Total of{workers}workers working concurrently')results=asyncio.run(run(workers))print(f'The{workers}workers processed a total of{sum(results)}items')if__name__=='__main__':main()Display messages from multiprocessing pool processes -example4This example demonstrates howlist2termcan be used to display messages from processes executing in amultiprocessing Pool. Each item of the list represents a background process. Thelist2term.multiprocessingmodule contains apool_mapmethod that fully abstracts the required multiprocessing constructs, you simply pass it the function to execute, an iterable of arguments to pass each process, and an optional instance ofLines. The method will execute the functions asynchronously, update the terminal lines accordingly and return a multiprocessing.pool.AsyncResult object. Each line in the terminal represents a background worker process.If you do not wish to use the abstraction, thelist2term.multiprocessingmodule contains helper classes that facilitates communication between the worker processes and the main process; theQueueManagerprovide a way to create aLinesQueuequeue which can be shared between different processes. Refer toexample4bfor how the helper methods can be used.Notethe function being executed must accept aLinesQueueobject that is used to write messages via itswritemethod, this is the mechanism for how messages are sent from the worker processes to the main process, it is the main process that is displaying the messages to the terminal. The messages must be written using the format{identifier}->{message}, where {identifier} is a string that uniquely identifies a process, defined via the lookup argument toLines.Codeimporttimefromlist2termimportLinesfromlist2term.multiprocessingimportpool_mapfromlist2term.multiprocessingimportCONCURRENCYdefis_prime(num):ifnum==1:returnFalseforiinrange(2,num):if(num%i)==0:returnFalseelse:returnTruedefcount_primes(start,stop,logger):worker_id=f'{start}:{stop}'primes=0fornumberinrange(start,stop):ifis_prime(number):primes+=1logger.write(f'{worker_id}->{worker_id}{number}is prime')logger.write(f'{worker_id}->{worker_id}processing complete')returnprimesdefmain(number):step=int(number/CONCURRENCY)print(f"Distributing{int(number/step)}ranges across{CONCURRENCY}workers running concurrently")iterable=[(index,index+step)forindexinrange(0,number,step)]lookup=[':'.join(map(str,item))foriteminiterable]# print to screen with lines contextresults=pool_map(count_primes,iterable,context=Lines(lookup=lookup))returnsum(results.get())if__name__=='__main__':start=time.perf_counter()number=100_000result=main(number)stop=time.perf_counter()print(f"Finished in{round(stop-start,2)}seconds\nTotal number of primes between 0-{number}:{result}")Other examplesA ConwayGame-Of-Lifeimplementation that useslist2termto display game to the terminal.DevelopmentClone the repository and ensure the latest version of Docker is installed on your development server.Build the Docker image:dockerimagebuild\-tlist2term:latest.Run the Docker container:dockercontainerrun\--rm\-it\-v$PWD:/code\list2term:latest\bashExecute the build:pyb-X