Home Building a Python REPL Themed Portfolio Website
Post
Cancel

Building a Python REPL Themed Portfolio Website

Building a Python REPL Themed Portfolio Website

Launched the initial version of my Python REPL-themed portfolio website (with an ASCII art photo gallery) 🚀.

site home page Home Page

Website: adarshd.dev or adarsh.pizza 🍕

Source code: github.com/adarshdigievo/Portfolio-REPL/

Internals

  • UI layer and Python layer - both executed in the client browser

The site can be considered to have two components. One is the terminal Ui which is powered by Xterm.js. Accepting user input, handling the events, and printing the results are handled using Xterm functions.

The second component is the Python interpreter which runs the code and returns the result to xterm for printing in terminal UI. This is created using Python’s built-in InteractiveInterpreter class found in code module of stdlib.

  • Loading the portfolio data

The portfolio data like skills, experience, etc. are fetched from the free API from Gitconnected and follow the open Jsonresume standard

  • Metaprogramming

I love metaprogramming and have (over)used some metaprogramming concepts on the site. I wanted the variables storing my portfolio to be dynamic. This is possible with Descriptors where we can call custom getters when an attribute is accessed.

Currently, I have mapped profile data variables passed to the REPL like SKILLS, EXPERIENCE, etc into attributes of the class named ProfileData and have created descriptor attributes using ProfileFetchDescriptor. This descriptor is responsible for calling Gitconnected API and reloading the data if it is expired.

I have stored all profile data fields in an enum named ProfileFields which is used multiple times when I need to list all available pre-loaded variables in the REPL.

1
2
3
4
5
6
attrs = {
        field.name.lower(): ProfileFetchDescriptor(field) for field in ProfileFields
    }

    # Dynamically generate a class named ProfileData. Each member attribute of the class is a descriptor
    ProfileData = type("ProfileData", (), attrs)
  • Redirecting stdout

I needed to get the output of code executed by InteractiveIntrpetor into a string so that it can be printed on the xterm ui. For that, redirect_stdout and redirect_stderr functions from contextlib were used.

1
2
3
4
5
6
7
import io
from contextlib import redirect_stdout

with io.StringIO() as buf, redirect_stdout(buf):
    print('redirected')
    output = buf.getvalue()

This output is then written on the xterm terminal.

  • Ascii Art powered photo Gallery

In the gallery code, I have used ascii magic library to convert images into colored text and it is printed directly to an xterm terminal.

To store image metadata, I have added a metadata json file which includes the path to image resources and the caption to display on each image. The page then does an infinite iteration over the image data displaying each of them one by one. I have not decided to cache the images in Python due to memory worries.

  • Opening links

To open links to my blog, photo gallery, etc, another descriptor is used. The descriptor uses webbrowser.open() to open the corresponding link. This Python functionality is supported out of the box in Pyscript. I have created an object of ProfileLinks class named VISIT

Code here

1
2
3
4
5
6
7
8
9
10
11
12
13
link_attrs = {
        field.upper(): ProfileLinkDescriptor(field)
        for field in ProfileLinkDescriptor.link_map
    }
    
    ProfileLinks = type("ProfileLinks", (), link_attrs)

    VISIT = (
        ProfileLinks()
    )  # This class contains descriptor attributes which open corresponding webpages on access. Ex: VISIT.BLOG
    
    interpreter.locals |= {"VISIT": VISIT}  # Load the class to interpreter locals

Learnings

Random new things I learned from this project:

  • Eruda - JS debugging on mobile browsers

The website had some weird input issues on Chrome-based Android browsers while testing. While searching for a tool to debug JS on mobile browsers, I found Eruda. Eruda will emulate the desktop browser dev console with almost all features like js console logs, network requests, etc and this was super helpful to debug on mobile devices.

  • Pyscript

Pyscript as their tagline says is ‘Programming for the 99%’. Pyscript makes it a lot easier to deploy an application with no manual configuration steps needed like pip installs and can be easily hosted as a static site. It is Python for the front end and will be well-suited for data applications (visualizations, graphics, etc).

  • Pyodide

The magic of Pyscript exists thanks to Pyodide and webassembly. Pyodide is a python runtime that runs in Webassembly. Pyscript uses Pyodide internally and it can be seen as a convenient wrapper over pyodide. It gets the python code from <py-script> tags and sends them to the interpreter (which is Pyodide by default, and can be customized) for execution. Refer makePyscript() or pyExec() in the Pyscript source code to see more on the same. Pyscript exposes the power of Pyodide in a simple and convenient manner.

I found the function and variable interoperability b/w Python and JS super convenient. We can call the functions/variables defined in Python using javascript and vice versa. I have used this in the photo gallery page, to print python ascii-magic library output to xterm terminal.

See the example:

1
2
3
4
5
6
7
8
9
10
11
// js file

var term = new Terminal({
    cursorBlink: false,
    fontSize:6,
    cols:200,
    rows:150
});

term.open(document.getElementById('terminal'));

1
2
3
4
5
6
7
8
# python file

from js import term  # the js object is now available in Python
  
# can access all js functions defined in xterm js library from Python!
term.clear()
term.write('some_string')

Here is the C Soruce code of the function to_js() which is used to convert Python calls as seen above to js and this is the function toPy() used to convert js objects to Python.

Planned updates for the next versions

Web (frontend) development is hard. There are a lot of layout & compatibility issues across multiple platforms and browsers.

When I tested the home page on mobile devices for the first time, inputs were not working on Chromium-based mobile browsers, but were working fine on Firefox focus. I finally ended up with code like this:

1
2
3
4
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
    // onKey event is not firing on android chromium based browser. This workaround is applied in that case.
    console.log('mobile device') }

Other tasks planned for coming versions:

  • Support multiline inputs (to support function and class definitions)

I guess I might need to switch to InteractiveConsole class of the code module instead of the currently used InteractiveIntrepretor class.

  • Switch to py-terminal

Switch to py-terminal plugin of Pyscript and perform all term-related tasks (managing history, xterm events, etc) in Python code itself. I can skip the load and init steps of xterm using js since pyscript now support xterm terminal out of the box. Looking to implement something along the lines of this Pyscript plugin(emscripten-shell)

  • Use a custom on-screen keyboard using kioskboard.js, in case of mobile devices to support arrow keys, command history, etc.

  • Develop a visual version of the website

  • Support pastes in REPL

  • ast based variable parsing

Currently, the profile data variables are mapped to the descriptor attributes using a simple and hacky string.contains() call here. I plan to first parse the input code into AST and replace the areas of variable access to use the descriptors.

Prior Art


photo gallery Ascii Photo Gallery

Visit the site: adarshd.dev

Photo gallery: adarshd.dev/gallery.html

Source code: github.com/adarshdigievo/Portfolio-REPL/

This post is licensed under CC BY 4.0 by the author.

It is said that, in Python everything is an object. I took that literally.

Thread Safety in Python