{"id":802,"date":"2025-10-21T00:03:36","date_gmt":"2025-10-21T07:03:36","guid":{"rendered":"https:\/\/konukoii.com\/blog\/?p=802"},"modified":"2025-10-21T17:06:22","modified_gmt":"2025-10-22T00:06:22","slug":"5-min-tutorial-building-pypi-packages","status":"publish","type":"post","link":"https:\/\/konukoii.com\/blog\/2025\/10\/21\/5-min-tutorial-building-pypi-packages\/","title":{"rendered":"5-Min Tutorial: Building PyPI Packages"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 4<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>\n<p>A few days ago, I was coding casually while re-watching some episodes of Bob\u2019s Burgers. Inspired by the show, and with a vague memory of an old SpongeBob web-game I use to play as a kid, I decided to create a simple burger-flipping game. While the game itself isn\u2019t particularly significant (I may end up sharing it later), what is important is that I needed a lot of images. Since I have Google\u2019s Gemini, I started using it. I would sketch a basic idea on paper and then prompt Gemini to re-draw it in a different style. As you can imagine, this process became quite tedious.<\/p>\n\n\n\n<p>Instead of continuing with the Gemini-based approach, I decided to build my own tool called <code>Imagenation<\/code>. Initially, it was a command-line tool, but I soon realized that I would likely need to reuse it in other projects. Therefore, I decided to learn how to create my own package and eventually upload it to PyPI. As a Python enthusiast, this was a long-standing curiosity of mine.<\/p>\n\n\n\n<p>At a high-level, the tool needed to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Generate images from text prompts (Text-to-Image).<\/li>\n\n\n\n<li>Modify existing images with text prompts (Text+Image-to-Image).<\/li>\n\n\n\n<li>Runs as a simple CLI tool that can chew through a CSV or JSON file of prompts.<\/li>\n\n\n\n<li>Works as an importable Python library for other projects.<\/li>\n<\/ol>\n\n\n\n<p><strong>Git Repo: <a href=\"https:\/\/github.com\/pmsosa\/Imagenation\">https:\/\/github.com\/pmsosa\/Imagenation<\/a><\/strong><\/p>\n\n\n\n<p><strong>PyPI: <a href=\"https:\/\/pypi.org\/project\/imagenation\/\">https:\/\/pypi.org\/project\/imagenation<\/a><\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Leveraging Google's Imagen<\/h2>\n\n\n\n<p>First things first: just talking to the API. <a href=\"https:\/\/ai.google.dev\/gemini-api\/docs\/imagen\">Google's generative AI tools<\/a> are incredibly powerful, and getting a single image generated in Python is refreshingly simple.<\/p>\n\n\n\n<p>You set up your API key, initialize the model, and essentially just\u2026 ask for what you want.<\/p>\n\n\n\n<p>First, go pick up your API key from: <a href=\"https:\/\/aistudio.google.com\/api-keys\">https:\/\/aistudio.google.com\/api-keys<\/a><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport google.generativeai as genai\n\n# Make sure your GOOGLE_API_KEY is an env variable\ngenai.configure(api_key=os.environ&#x5B;&quot;GOOGLE_API_KEY&quot;])\n\nmodel = genai.GenerativeModel(&#039;gemini-pro-vision&#039;) # Or your model of choice\n\nresponse = model.generate_content(\n    &#x5B;\n        &quot;A beautiful sunset over mountains&quot;,\n        PIL.Image.open(&quot;my-image.png&quot;) # Optional input image\n    ]\n)\n\n# ...then you have to process the response, get the bytes, save to a file...\n<\/pre><\/div>\n\n\n<p>The first time this works, it feels like magic. But the <em>second<\/em> time you run it\u2014and the third, fourth, fifth, and sixth time, all within a minute\u2014you hit the real boss: <strong>Rate Limiting<\/strong>.<\/p>\n\n\n\n<p>The free tier for the API (at the time of writing) allows about 5 requests per minute. If you send that sixth request, you get a <code>429<\/code> error, and your script comes to a crashing halt.<\/p>\n\n\n\n<p>I added a <code>--delay<\/code> parameter that defaults to 12 seconds (just enough for 5 requests\/min), but you can set it to <code>0.2<\/code> if you're on a paid tier (300 requests\/min) or <code>15.0+<\/code> if you're running a massive batch and want to be extra careful.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">From Script to Package<\/h2>\n\n\n\n<p>The project could have ended there. I had a <code>imagenation.py<\/code> script that worked for me. But I've been doing this long enough to know that \"future me\" is going to want to use this again, and he's not going to want to hunt for a stray <code>.py<\/code> file. He's going to want to <code>pip install<\/code> it.<\/p>\n\n\n\n<p>This was the part I was most excited about: turning this pile of code into a real, distributable PyPI package.<\/p>\n\n\n\n<p>It turns out, with modern Python tooling, this is way more accessible than I thought. The process was basically:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Restructure:<\/strong> Stop putting everything in one file. I split the logic into:\n<ul class=\"wp-block-list\">\n<li><code>imagenation\/generator.py<\/code>: A <code>ImagenationGenerator<\/code> class that holds the core logic (generating, rate limiting, saving files).<\/li>\n\n\n\n<li><code>imagenation\/cli.py<\/code>: All the <code>argparse<\/code> stuff to handle command-line inputs.<\/li>\n\n\n\n<li><code>imagenation\/__main__.py<\/code>: To make the package runnable with <code>python -m imagenation<\/code>.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Configure <code>pyproject.toml<\/code><\/strong>: This is the new-ish, all-in-one config file. You define your project name, version, dependencies, and entry points. This file (along with <code>setup.py<\/code> or <code>setup.cfg<\/code> for compatibility) tells tools like <code>pip<\/code> and <code>twine<\/code> (the uploader) what your package is and how to build it.<\/li>\n\n\n\n<li><strong>Build &amp; Upload<\/strong>: Once the structure was right, the magic commands were:<\/li>\n<\/ol>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# Build the package\npython -m build\n\n# Upload the PyPI (TestPyPI first!)\n# Get you API keys at: https:\/\/test.pypi.org\npython -m twine upload --repository testpypi dist\/*\n\n# Upload to PyPI\n# Get your API keys at: https:\/\/pypi.org\npython -m twine upload dist\/*\n<\/pre><\/div>\n\n\n<p>The first time I typed <code>pip install imagenation<\/code> and it <em>actually worked<\/em>... that was a good feeling.<\/p>\n\n\n\n<p>Now, the project has its final, intended forms.<\/p>\n\n\n\n<p>As a <strong>library<\/strong> you can just import:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom imagenation import ImagenationGenerator\n\n# Initialize with a faster delay for a paid tier\ngen = ImagenationGenerator(rate_limit_delay=0.2) \n\n# Generate a text-to-image\ngen.generate_text_to_image(&quot;A serene lake at sunset&quot;, &quot;lake.png&quot;)\n\n# Generate a text+image-to-image\ngen.generate_text_image_to_image(\n    &quot;Make this photo look vintage&quot;, \n    &quot;input.jpg&quot;, \n    &quot;vintage_output.jpg&quot;\n)\n<\/pre><\/div>\n\n\n<p>And as the <strong>CLI tool<\/strong> it was always meant to be:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# Generate a single image\n.\/start.sh -it &quot;A raccoon flying a Cessna 152&quot; -o flying_raccoon.png\n\n# Process a whole batch of ideas from a CSV file\n.\/start.sh --csv batch_data.csv\n\n# Or from a JSON file\n.\/start.sh --json batch_data.json\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">Just a Fun Project<\/h3>\n\n\n\n<p>In the end, this was one of those perfect micro-projects. It solved a genuine (albeit minor) problem I had, provided me with a tangible reason to learn two new things (the Imagen API and PyPI publishing), and resulted in a tool that I\u2019ll actually use again.<\/p>\n\n\n\n<p>It may not be a grand project, but it serves as a simple pretext to familiarize myself with PyPI. It\u2019s not intended for mastery, but rather for exploration. And, hey, it\u2019s significantly more enjoyable than spending an entire day copying and pasting prompts.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1023\" height=\"511\" src=\"http:\/\/konukoii.com\/blog\/wp-content\/uploads\/2025\/10\/imagenation.png\" alt=\"\" class=\"wp-image-806\" srcset=\"https:\/\/konukoii.com\/blog\/wp-content\/uploads\/2025\/10\/imagenation.png 1023w, https:\/\/konukoii.com\/blog\/wp-content\/uploads\/2025\/10\/imagenation-300x150.png 300w, https:\/\/konukoii.com\/blog\/wp-content\/uploads\/2025\/10\/imagenation-768x384.png 768w\" sizes=\"auto, (max-width: 1023px) 100vw, 1023px\" \/><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>A few days ago, I was coding casually while re-watching some episodes of Bob\u2019s Burgers.&#8230;<\/p>\n<div class=\"more-link-wrapper\"><a class=\"more-link\" href=\"https:\/\/konukoii.com\/blog\/2025\/10\/21\/5-min-tutorial-building-pypi-packages\/\">Read the post<span class=\"screen-reader-text\">5-Min Tutorial: Building PyPI Packages<\/span><\/a><\/div>\n","protected":false},"author":1,"featured_media":817,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[32],"tags":[33,128,132,129,131,38,26,126],"class_list":["post-802","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-5-min-tutorials","tag-ai","tag-computer-vision","tag-imagen","tag-pypi","tag-python","tag-tutorial","tag-vibecoding","excerpt","zoom","full-without-featured","even","excerpt-0"],"_links":{"self":[{"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/posts\/802","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/comments?post=802"}],"version-history":[{"count":17,"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/posts\/802\/revisions"}],"predecessor-version":[{"id":821,"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/posts\/802\/revisions\/821"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/media\/817"}],"wp:attachment":[{"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/media?parent=802"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/categories?post=802"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/konukoii.com\/blog\/wp-json\/wp\/v2\/tags?post=802"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}