Charlie Luo

07 Jul 2020


Making a Pip Package

In the past, I built a module for another project for an interactive data display with Discord embeds. Last week, I realized that this would be a perfect project to convert into a pip module, so I spent my weekend building Discordbook.

Conception

In the original project, I had a large list of items, and I wanted to display them all, alphabetically, for users to reference. However, Discord embeds have a character, and even if I could put all the items in a single embed, the result would be unreadable. I took inspiration from a (degenerate) bot I had seen before that used “pages” of content, with a user adding reactions to browse through. I built my own version, resulting in this system:

I built some custom classes, calling it a Book. However, my implementation was shoddy - I ran into classic indexing errors while working around Discord’s embed system, mostly because I was trying to keep the class free of Discord calls to avoid complication. I also was trying to allow the book to be dynamic, resulting in some weird code:

for chapter in self.chapters :

            chapter_content = self.chapters[chapter]

            # Find which chapter the page is on
            if index + len(chapter_content) < PER_PAGE * page_number :
                index += len(chapter_content)


            else :
                content = ""

                # Index is now in the chapter
                start = start_display - index if start_display - index > 0 else 0
                end = end_display - index if end_display - index < len(chapter_content) else len(chapter_content)


                for i in range(start, end) :
                    content += str(chapter_content[i]) + '\n'

                embed.add_field(name = chapter, value = content, inline = False)

                if end_display - index < len(chapter_content) :
                    break

                index += len(chapter_content)

At the time, I settled for a working implementation of my book, and used it in the original project. However, it remained a bit buggy, as well as completely developer unfriendly. The original project was also written in discord.py 0.16.x, an old branch that was no longer being supported. Despite these issues, the system was working, so I took my focus off the module idea for a few months.

Conversion

When I came back to this idea, I knew I had to fix some major issues before even considering making a the project a module:

These issues, notably book arguments, necessitated a complete rewrite of the code. I spent my first couple hours building a new class called Chapter and integrating it with my rewritten book code. This idea was to make the development experience much simpler. Before, building the book looked like this:

items.sort()
chapters = {}
for i in range(65, 91) :
    chapters[chr(i)] = []

for item in items :
    chapters[item.name[0]].append(item)

After, it looked like this:

items.sort()

item_book = AlphabeticalBook(items, title = "**Item List**", description = "**Hit the reaction buttons to go forwards or backwards!**", per_page = 20)

After fixing those issues, I wrote a new system to generate pages for books, making them entirely static. Though I didn’t like this trade-off, it made browsing considerably less buggy by simplifying the process, though I want to eventually build a dynamic book system. With pregenerated pages, combined with a more modern discord.py, making the book self-contained was easy. With those changes, I tried my best to make intuitive systems, with the idea in mind that other people may eventually look at this code. For me, it was a great project to reinforce best practices with projects like this, especially with code readability.

Completion

To finish the package, I followed some tutorials online to publish my package to pip. Once the package was published, I could include it in my original project. Unfortunately, I had to refactor the original code to be compatible with the new version of discord.py I was using, which took quite a few hours. I spent a lot of time going between that project and my module, rewriting code from both sections and publishing several different releases for bugs I found while developing the other project. I think this was the worst mistake I made here; I should have been more thorough in debugging and fleshing out the package before publishing and releasing, but I was impatient to integrate, so I rushed the process.

Overall, however, this was one of the most satisfying projects I’ve worked on. I set out with a solid objective, knew exactly what problems I would be solving, and was able to accomplish all the goals I wanted to achieve. It was a short project, but I’ll definitely be updating and improving it in the future. I have a bunch of ideas for a dynamic book, more edge cases I know I haven’t fixed, and many other random ideas floating around. Even if noone uses the module, I feel good about publishing my own pip package, and I’m pretty happy with my work.

Code can be found here, and the PyPi release can be found here.