Code and Life

Programming, electronics and other cool tech stuff

Supported by

Supported by Picotech

Stop Writing Scripts: Create and automatically run ChatGPT generated Python code

I previously covered how to run your own ChatGPT script with Python and Golang. But what if you want to create a script that automatically runs the ChatGPT generated code? That is what I will cover in this post. The idea is really simple:

  1. Create a script that asks for user input
  2. Pass the input to ChatGPT
  3. Run the ChatGPT generated code

NOTE: Please read the caveats at the end of the post before using this!

Calling The OpenAI API

First step is simple, just call the OpenAI API with the user input. If "-v" is given as the first argument, print the raw answer and usage statistics as well.

#!/usr/bin/python3

import openai
import sys

openai.api_key = 'sk-xxx'

verbose = sys.argv[1] == '-v'
prompt = ' '.join(sys.argv[2 if verbose else 1:])

resp = openai.ChatCompletion.create(
  model="gpt-3.5-turbo",
  messages=[
        {"role": "system", "content": "You are Python code generator. Answer with just the Python code."},
        {"role": "user", "content": prompt},
    ]
)

data = resp['choices'][0]['message']['content']

if verbose: print(data, 'Usage was:', resp['usage'], sep='\n')

Parsing the Code From Response

A rather simplistic implementation looks for set of start tokens and end tokens and returns the code between them. This is not perfect, but it works as long as the ChatGPT response does not contain multiple code blocks.

# Extract lines of code from the response tagged with ``` or ```python
lines = []
ok_start = ['```', '```python', '```py', '```python3', '```py3']
started = False
for line in data.splitlines():
    if not started:
        if line.strip() in ok_start:
            started = True
    else:
        if line.strip() == '```': break
        lines.append(line)

Running The Code

The code is then written to a temporary file and executed. After running, the file is deleted.

import os
import tempfile

# Create a temporary file
with tempfile.NamedTemporaryFile(mode='w', delete=False) as tf:
    tf.write("#!/usr/bin/env python3\n")

    # Write lines to the file
    for line in lines:
        tf.write(line + '\n')

# Make the file executable
os.chmod(tf.name, 0o755)

# Run the file with python3
os.system(tf.name)

# Delete the file
os.unlink(tf.name)

Trying It Out

The whole script is available here for your convenience. If you are on Linux, you can chmod +x the script and run it directly. Otherwise, you can run it with python3 pyai.py:

$ pyai -v simple hello world app
print("Hello, world!")
Usage was:
{
  "completion_tokens": 6,
  "prompt_tokens": 30,
  "total_tokens": 36
}
Hello, world!

Omitting the -v argument will only run the generated code:

$ pyai print out 10 first primes
2
3
5
7
11
13
17
19
23
29
$ pyai calculate sha1 checksum of file pyai
SHA1 checksum of pyai: db5ab334ca2af7508460d5ade8dac7fedd48bcf0
$ sha1sum pyai
db5ab334ca2af7508460d5ade8dac7fedd48bcf0  pyai

Python has pretty powerful libraries, so you can access the web as well:

$ pyai print out longest h1 tag content text from https://codeandlife.com
Using a Soundcard as an Oscilloscope to Decode 433 MHz Smart Power Plug Remote Control Signals
$ # If you want to know what the code (and usage) was, use -v in the beginning
$ pyai -v print out longest h1 tag content text from https://codeandlife.com
```python
import requests
from bs4 import BeautifulSoup

url = "https://codeandlife.com"
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")

h1_list = soup.find_all("h1")
longest_h1 = max(h1_list, key=lambda x: len(x.text))

print(longest_h1.text.strip())
```
Usage was:
{
  "completion_tokens": 76,
  "prompt_tokens": 41,
  "total_tokens": 117
}
Using a Soundcard as an Oscilloscope to Decode 433 MHz Smart Power Plug Remote Control Signals

Yes you can read files too:

$ pyai print out contents of /etc/passwd
# And the contents of /etc/passwd come here :D

$ pyai read /etc/passwd, split by colon and print the largest 3rd entry value
65534

Conclusions and Caveats

While the code is simple and has many limitations, it is staggeringly powerful. You can do pretty much anything you can do with Python with this. But the downside is it's like the "I'm feeling lucky" version of scripting. You don't know what the code is going to do. You can't even see the code before running it. So, use this with caution! Potential drawbacks may include, but are not limited to:

  1. Racking up your OpenAI API usage and costing you money
  2. ChatGPT getting it wrong and the code not working
  3. Code working but doing something you didn't expect
  4. Overwriting (important) files
  5. Getting really malicious code that does something bad

Currently code injection to LLMs is pretty hard, but it is not unthinkable that someone will find a way to do it. So, please be careful when using this. I am not responsible for any damage caused by this script!

Read post

Python on-the-fly AES encryption/decryption and transfer to AWS S3

So, I started writing a file database and toolset called fileson to take advantage of AWS S3 Glacier Deep Archive (let's just call it GDA from now on). With 1 €/mo/TB storage cost, it is essentially a dirt cheap option to store very infrequently accessed data like offsite backups.

Why not just use rclone? Well, I disliked the fact that all tools do a ton of (paid) queries against S3 when syncing. I thought a simple JSON file database should work to keep track what to copy and delete. Well, that work is progressing, but as a part of that...

Encrypting on the fly with Python and Pycrypto(dome)

I started thinking that client side encryption would be useful as well. AES is tried and tested, and it's easy to find sample code to do it. But it seems wasteful to first create encrypted files on your hard drive, then upload them to AWS and finally delete everything.

Luckily, the Python AWS SDK boto3 has a great example on how to upload a file to S3 with upload_fileobj that accepts "a readable file-like object". What does that mean? Let's find out!

Read post

Project Euler Problem 2 and 3 with Clojure, Python, Javascript and C++

Continuing from my Project Euler Problem 1, here are the next two Euler problems and some reflection!

Project Euler Problem 2

The second problem asks to find the sum of even Fibonacci numbers numbers below four million. For procedural languages, this is quite walk in the park, but for me a concise method to produce the series in Clojure proved a bit of a brain nugget!

Python 3

For Python, I decided to make a simple generator that yields the Fibonacci numbers below a given treshold N. Rest is just list comprehension:

def fib(N):
    i, j = 0, 1
    while j < N:
        yield(j)
        i, j = j, i+j

print(sum(i for i in fib(4e6) if i%2==0))

C++

In C++ it's natural to just loop with a conditional addition. This time I decided to drop using namespace std; to save a few characters. Without i, j = j, i+j syntax, the temporary variable ij is a bit ugly.

#include <iostream>

int main() {
    int i=0, j=1, s=0;

    while(j<4000000) {
        if(j%2==0) s += j;
        int ij = i + j; i = j; j = ij;
    }
    std::cout << s << std::endl;
}
}

Read post

Project Euler Problem 1 with Clojure, Python, Javascript and C++

I've long thought about learning Lisp. Some time ago I ran across Clojure and the map, set and vector implementations felt like a modern addition. It's running on top of JVM which is nice in a sense, but a major pain in the ass when installing. Well, I swallowed my wow to never install JDK again and got Clojure running quite painlessly.

To learn the ropes, I thought it would be a fun idea to rewrite Project Euler problems 1-10 with Clojure to see how it would compare against my language of choice for these types of algorithmic problems, Python.

Just to make it more fun, I decided to implement them in my second favorite language, Javascript as well. Since ECMAScript 9, it's actually a pretty powerful and concise language, and coupling it with Node.js unleashes some serious usage options outside just web pages. Npm dependency hell sucks, but that's a topic for another post. For more bare metal, I'm also doing C++ (basically sticking to C functionality until maps and sets make themselves useful). Let's see how the languages stack up!

Project Euler Problem 1

The first problem in Project Euler is simple, add up numbers below 1000 that are divisible with either 3 or 5 (or both). Modulo operator % can be used to get the remainder of a number when divided by 3 and 5, and basically we add all numbers that have either modulo as zero.

Python 3

With Python, the most concise implementation is to use the sum(iterable) and a list comprehension, netting us a nice one-liner:

print(sum(i for i in range(1000) if i%3==0 or i%5==0))

Read post

How to calculate PBKDF2 HMAC SHA256 with Python, example code

Having just spent 4 hours trying to get a Python pseudocode version of PBKDF2 to match with hashlib.pbkdf2_hmac() output, I thought I'll post Yet Another Example how to do it. I thought I could just use hashlib.sha256 to calculate the steps, but turns out HMAC is not just a concatenation of password, salt and counter.

So, without further ado, here's a 256 bit key generation with password and salt:

import hashlib, hmac

def pbkdf2(pwd, salt, iter):
    h = hmac.new(pwd, digestmod=hashlib.sha256) # create HMAC using SHA256
    m = h.copy() # calculate PRF(Password, Salt+INT_32_BE(1))
    m.update(salt)
    m.update(b'\x00\x00\x00\x01')
    U = m.digest()
    T = bytes(U) # copy
    for _ in range(1, iter):
        m = h.copy() # new instance of hmac(key)
        m.update(U) # PRF(Password, U-1)
        U = m.digest()
        T = bytes(a^b for a,b in zip(U,T))
    return T

pwd = b'password'
salt = b'salt'

# both should print 120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b
print(pbkdf2(pwd, salt, 1).hex())
print(hashlib.pbkdf2_hmac('sha256', pwd, salt, 1).hex())

# both should print c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a
print(pbkdf2(pwd, salt, 4096).hex())
print(hashlib.pbkdf2_hmac('sha256', pwd, salt, 4096).hex())

Getting from pseudocode to actual working example was surprisingly hard, especially since most implementations on the web are on lower level languages, and Python results are mostly just using a library.

Simplifying the pseudo code further

If you want to avoid the new...update...digest and skip the hmac library altogether, the code becomes even simpler. HMAC is quite simple to implement with Python. Here's gethmac function hard-coded to SHA256 and an even shorter pbkdf2:

Read post

WebSocket Magic: Create a Simple Server and Client in Go and JavaScript

Title image, generated with Stable Diffusion

WebSocket is a protocol that allows for real-time, bidirectional communication between a client and a server. It is often used in web applications to enable features such as chat, live updates, and multiplayer games.

In this tutorial, I will show you how to create a minimalistic WebSocket server using Go and the nhooyr websocket library, and a JavaScript client to test it out. You will learn how to handle WebSocket connections, send and receive messages, and close the connection when necessary.

By the end of this tutorial, you will have a working WebSocket server and client that you can use as a starting point for your own WebSocket-based applications.

Setting up the project

You should first set up a simple "Hello world" go project, something along the lines of this tutorial. After you have a project going, let's install nhooyr.io/websocket WebSocket library (Go's own seems deprecated and Gorilla development has ceased some years ago):

$ go get nhooyr.io/websocket

The whole system will consist of main.go that will contain a simple net/http server that will:

  1. Serve a simple WebSocket echo server at /echo
  2. Serve static files from static subfolder – essentially other addresses including / will try content from there. We'll put index.html under that subfolder.

Basic webserver stuff:

func main() {
	address := "localhost:1234"
	http.HandleFunc("/echo", echoHandler)
	log.Printf("Starting server, go to http://%s/ to try it out!", address)
	http.Handle("/", http.FileServer(http.Dir("static")))
	err := http.ListenAndServe(address, nil)
	log.Fatal(err)
}

Now the echoHandler will do a few essential items:

  1. Upgrade the connection into a WebSocket one with websocket.Accept
  2. Log errors and defer connection close in case of errors
  3. Loop forever (or actually 10 minutes in this sample), reading messages from the socket and writing them back.

Note that I've used InsecureSkipVerify to accept connections from any origin, you might want to modify the code for a tighter policy:

Read post