Python Project Management - Pypi Upload

Updated Oct 03, 2024

0. Purpose

The purpose of this blog is to show just simplified version of how to start developing python project upload it to pypi. When I googled, I found that there are many ways to to, but many of them are ambigious. So, I’ll try to write a detailed blog about it.

1. Project Structure

It’s just a simple project structure.

1
2
3
4
5
6
7
8
9
10
.
├── My_Project 
│   ├── __init__.py
│   ├── dataclass.py
│   ├── main.py
│   └── util.py
├── README.md
├── pyproject.toml
├── sample
└── setup.py

In my own opinion, split main code and utility code is good to maintain the code. And also, from python@3.7, we can use dataclass (like struct in C). So, I recommend to use it for readability and accessibility.

1.1. Project Code Structure

1
2
# My_Project/__init__.py
from .main import MyClass

for python packaging, we need to have __init__.py file in the directory.

1
2
3
4
5
6
7
8
9
10
# My_Project/dataclass.py
from dataclasses import dataclass
from typing import List, Optional

@dataclass
class MyData:
    name: str
    age: int
    address: Optional[str] = None
    friends: List[str] = None

You can cusomize the dataclass as you want.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# My_Project/main.py
from .util import MyUtil

class MyClass:
    def __init__(self):
        self.util = MyUtil()

    def print(self):
        print("MyClass")
        self.util.print()

if __name__ == "__main__":
    my_class = MyClass()
    my_class.print()

I usually use main.py to write main Class and test it.

1
2
3
4
5
6
7
8
9
10
# My_Project/util.py
def print():
    print("test1234")

class MyUtil:
    def __init__(self):
        pass

    def print(self):
        print("MyUtil")

I just use util.py to write my custom classes and functions. But, you can split it as you want functionally as you want.

1.2. pyproject.toml and setup.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[project]
name = "My-Project"
version = "0.0.1"
description = "My Simple Testing Project"
readme = {file = "README.md", content-type = "text/markdown"}
dependencies = ["requests", "tqdm"]
maintainers = [{name = "h1ghl1kh7", email = "h1ghl1kh7@gmail.com"}]
license = {text = "MIT License"}

[tool.setuptools]
packages = ["My_Project"] # project folder name

[project.urls]
Repository = "https://github.com/h1ghl1kh7/My_Project"

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

It is a simple pyproject.toml file, and when I uploaded my own project, I used this format. It you want to know more about pyproject.toml, you can refer to Python Packaging User Guide.

The content of setup.py is very simple. It’s because I use pyproject.toml to manage the project (You don’t write anything in setup.py if you write pyproject.toml).

1
2
3
4
5
# setup.py
from setuptools import setup

if __name__ == "__main__":
    setup()

2. Upload to Pypi

Register to pypi.org Get api from (Account Settings -> API Tokens)

When you upload the project, it asks you to input token every time.

So, I recommend to write token into ~/.pypirc file.

1
2
3
[pypi]
  username = __token__
  password = pypi-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

If then, it automatically pass the token check process with this information.

1
2
python3 setup.py sdist bdist_wheel
python3 -m twine upload dist/*

Execute the above commands, package is built and uploaded.

It automatically create new project on pypi, so, you don’t have to create project on site.

And, I recommend to remove dist, build, My_Project.egg-info directories and rebuild the project when you upload another version of the project.