Working with GitHub Copilot as your AI pair programmer

Christopher Harrison - Feb 6 - - Dev Community

Recently I was building out a demo which used Django. I wanted to streamline the setup of the demo with a starter script to create and populate the database. This task fell squarely under the "I know this is possible but can't quite remember how to do it" umbrella. Normally I'd be stuck digging through the docs, copying and pasting snippets, and hoping the "Franken-code" I was building would actually work.

Instead I was able to rely on generative AI to build out my sample data and GitHub Copilot to generate the code. I was honestly amazed at how much work Github Copilot was able to do for me. It also served as a great use case in how to best prompt and work with Copilot.

The scenario

The sample app I was building is a website for a conference. I needed to create a setup script which would:

  1. Check to see if a local SQLite database was already created.
  2. Assuming no database existed, run the migration scripts to create the database.
  3. Prompt the user for their name, email and password, and create a superuser with this info.
  4. Populate the database with the users and talks.

Again, these are all things I know are possible, but just am not sure how to do it. I also didn't want to create a bunch of data on my own. It was time to rely on a little bit of AI.

Creating the data

While it's possible for GitHub Copilot to generate text (it does support Markdown, for example), it wasn't going to be the appropriate tool to create a bunch of sample data in CSV files. So I turned to ChatGPT to ask it to create CSV files for me, one with a list of names for my speakers, and another with a list of talks. The latter task I walked through a few times to create different tracks for a full collection of talks. While it took a little bit of manipulation, and I had two CSV files ready to go in just a few minutes.

Takeaway: GitHub Copilot is a tool, and just like every tool it has its best uses. Generating CSV files isn't an ideal scenario for Copilot, but it worked perfectly with ChatGPT.

Creating the file structure

Django supports custom commands which can be called just as you would the built-in commands like createsuperuser or startapp. That much I knew; I just didn't know how to actually do it. So I asked chat in VS Code "How do I create custom scripts in Django?"

Screen grab showing

Chat gave me information about how to create the file and folder structure necessary for the command, even though I used the wrong terminology. While I'm certainly going to increase the chances I get the right answer by using the right terminology, I appreciated the bit of flexibility offered to me by Copilot.

The answer provided says to put the code into an existing folder, but I really want this to be separate. So I created a new folder in the project and duplicated the structure in as indicated in the chat reply. The results looked like this:

  • scripts/
    • init.py (empty file)
    • management/
      • init.py (empty file)
      • commands/
        • init.py (empty file)
        • demo-setup.py (where my code will go)

I finished the initial setup by registering the application, something I was already familiar with based on my experience with Django.

Takeaway: GitHub Copilot is not a replacement for developers. It won't do everything for you, and you won't be able to suddenly program in a language or framework you've never seen just by using Copilot. But it'll certainly help augment your understanding and aid your learning!

Creating the scaffolding

OK, project structure setup. Now it was time to start writing code. Coding is always an iterative process, regardless of the toolset we're using. So my first goal is to get some code into place, then start updating and adding features as needed.

Upon opening the newly created demo-setup.py file, I used Cmd-I to open the inline chat window in VS Code. I described at a high level what I was trying to do so I could get some code to start.

The prompt:



Create a custom command which checks if the sqlite database exists, and if not runs migrations, prompts for superuser information, and populates database from CSV files


Enter fullscreen mode Exit fullscreen mode

The initial code started with this:



import os
import csv
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from django.db import connection
from myapp.models import MyModel

class Command(BaseCommand):
    help = 'Check if SQLite database exists, run migrations, prompt for superuser info, and populate database from CSV files'

    def handle(self, *args, **options):
        # Check if SQLite database exists
        if not os.path.exists('db.sqlite3'):
            # Run migrations
            self.stdout.write('Running migrations...')
            connection.prepare_database()


Enter fullscreen mode Exit fullscreen mode

Takeaway: One of the best use cases for Copilot is to generate scaffolding. Pretty much every framework available has some level of ceremony, some set of boilerplate code which needs to be built out. Let Copilot generate that and the initial code, then you can focus on the more important pieces.

Improving the code

The code provided was a good first iteration. But I really prefer to have helper functions rather than a single function for the entire script. I find it's more readable and easier to maintain. So, back to Copilot Chat I went. I highlighted the code in my Python file and asked chat "Please refactor this to use helper functions."

While the "please" certainly isn't necessary, the output was exactly what I wanted. To focus on the handle function, which is the entry point to the command, it's now calling a bunch of helper functions, all of which were created by Copilot.



def handle(self, *args, **options):
    if not os.path.exists('db.sqlite3'):
        self.run_migrations()
        self.create_superuser()
        self.populate_database()
    else:
        self.stdout.write('SQLite database already exists.')


Enter fullscreen mode Exit fullscreen mode

Takeaway: The goal of Copilot is to aid developers as they write code. Just as the first iteration I write won't typically be the final product, the same holds true with Copilot. I'm constantly going back to make updates to what I write, a process which continues with Copilot. The fact the initial iteration was written by Copilot for me saves me a lot of time, and editing what's there is faster than me writing it all from scratch.

Customizing for my environment

The application I built uses a custom user object, rather than the default User in Django. The line from django.contrib.auth.models import User is going to import said generic User, not the custom one.

As already highlighted, coding is an iterative process. Copilot already did a lot of work here, now I just need to begin making some changes to get the code closer to what I want.

I added removed the import statement, and added in a couple of comments:



# Get the custom user model


Enter fullscreen mode Exit fullscreen mode

which generated:



from django.contrib.auth import get_user_model


Enter fullscreen mode Exit fullscreen mode

And then:



# Set User to the custom user


Enter fullscreen mode Exit fullscreen mode

which gave me:



User = get_user_model()


Enter fullscreen mode Exit fullscreen mode

Takeaway: Specificity and context matters. Because the only file I had open was the script file, Copilot was unaware I was using a custom User. With a couple of quick comments where I provided more information, I was able to update the code to get the custom user.

Fast forwarding to the end

From there, it was time to flesh out the script, add in the necessary logic, and build the rest of the functionality. Through the process I relied on code completion (ie: adding comments to my code) when I had a clear understanding of what I needed, and chat when I needed to ask questions and be a bit more interactive.

In the course of about an hour, with me admittedly on a conference call which I was "monitoring", I was able to generate about 70 lines of code (counting comments). You can see the finished command. I cleaned up a few comments, which I typically don't do (not really sure why I did that in this case).

You'll notice in the last function which populates the database I gave a pretty long set of comments to describe exactly what I wanted to do. This is certainly a lot of typing, but given I wasn't sure how to do it off the top of my head, it was much faster than the traditional methods of going to Google.

The last thing you might see is a relatively large delta from some of the names used in the initial code and the final result. This is largely due to how I write code, that I typically need to see things to help crystalize in my brain how I actually want them implemented. It's also one of the things I really like Copilot for; it allows me to just try things, to prototype, and then go back and change them as I figure out what it is I'm trying to do.

Summary

I've always liked the tagline of GitHub Copilot as "Your AI pair programmer." I find if you think of it like that, you tend to get the best results. When I'm working with someone else I don't expect them to have all the answers, just as I don't either. We'll work together to generate the necessary code, working iteratively, trying things, changing things, until we get the result we need.

In this example here, I started by effectively saying, "I know this is possible... How do I do this?", and I was off and running. I wrote some code, I asked some questions, I made some updates. And in less than an hour I had the entire script/command built out. Copilot has streamlined the way I write code.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .