Contents

Notes on Creating Python Modules

Cover image was generated by ChatGPT and edited by the author.

Recently, I’ve been learning how to package Python code for quick sharing and easy installation. After exploring various distribution methods, I realized that creating a Python module is probably one of the most convenient options, mainly because it saves me from having to reinstall everything repeatedly.

The following section documents my creation process in written form.

Writing the Module

When it comes to preparing for packaging a module, the first step is, of course, to have a working module ready.

Recently, I’ve been experimenting with porting the spatial interpolation tool autoFRK, originally developed by Wen-Ting Wang based on the paper Resolution Adaptive Fixed Rank Kriging by Sheng-Li Tzeng and Hsin-Cheng Huang (link), from R to Python.

The goal is to enable the model to be used within deep learning environments built on PyTorch.

A portion of the module’s source code is shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# import modules
import torch
import torch.nn as nn
from typing import Optional, Union
from autoFRK.utils.logger import setup_logger
from autoFRK.utils.device import setup_device
from autoFRK.utils.utils import *
from autoFRK.utils.predictor import *

# logger config
LOGGER = setup_logger()

# class AutoFRK
class AutoFRK(nn.Module):
    """
    Automatic Fixed Rank Kriging

    This function performs resolution-adaptive fixed rank kriging on spatial
    data observed at one or multiple time points using a spatial random-effects
    model.

    ...
    """
    def __init__(
        self,
        mu: Union[float, torch.Tensor]=0.0, 
        D: torch.Tensor=None, 
        G: torch.Tensor=None,
        finescale: bool=False, 
        maxit: int=50, 
        tolerance: float=1e-6,
        maxK: int=None, 
        Kseq: torch.Tensor=None, 
        method: str="fast", 
        n_neighbor: int=3, 
        maxknot: int=5000,
        dtype: torch.dtype=torch.float64,
        device: Optional[Union[torch.device, str]]=None
        ):
        """
        Initialize autoFRK model with tensor-safe and device-aware configuration.
        """
        super().__init__()
        
        ...

Preparation

Before packaging the module, we need to organize the module files.
The directory structure should look like this:

This is the project directory of the module.

Within the project_dir/src/module_name/ directory, all the module’s source code is placed. Each subdirectory must include an __init__.py file to declare the directory as a Python module, allowing Python to correctly import its contents.

At the root of the project, there are usually several auxiliary files and folders, such as tests/ for unit or integration tests; pyproject.toml for defining the project’s basic information, dependencies, and build configuration; README.md as the project’s documentation; LICENSE for the module’s licensing terms; and setup.cfg or setup.py for compatibility with older packaging methods (optional). Additionally, MANIFEST.in specifies extra non-code files to include during packaging, such as documents, data files, or static resources. These files form the structure required for module distribution, keeping the project organized and consistent during development, testing, and packaging.

All operations below are performed on Windows 11.

In this module, the pyproject.toml and MANIFEST.in are as follows:

pyproject.toml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "module_name"
version = "module_version"
description = "module_description"
readme = "README.md"
requires-python = ">=3.10"
license = { file = "LICENSE" }
authors = [
  { name="author_name", email="email_address" }
]

dependencies = [  # required dependencies
  "numpy>=1.23",
  "torch>=2.0",
  "scipy>=1.10",
  "faiss-cpu>=1.7.4",
  "scikit-learn>=1.3",
  "colorlog>=6.7",
]

[project.optional-dependencies]
gpu = [  # dependencies for GPU version
  "faiss-gpu>=1.7.4"
]

[project.urls]
Homepage = "Github_project_URL"
Issues = "Github_issues_URL"

[tool.setuptools.packages.find]
where = ["src"]
include = ["module_name*"]

MANIFEST.in

1
2
3
include README.md
include LICENSE
recursive-include src/module_name *.py *.txt *.md

Packaging the Module

Before packaging, we first need to install the packaging tool. Run the following command in the terminal to install the build tool:

1
pip install --upgrade build

build is the Python-recommended packaging tool, which performs the following tasks:

  • Reads the configuration from pyproject.toml.
  • Uses setuptools to automatically find packages (find_packages).
  • Converts the source code directory (e.g., project_dir/src/) into distributable package files.

After running the build, the system will generate two common types of distribution files in the dist/ directory:

  • .whl
    A Wheel file, which is a binary distribution.
  • .tar.gz
    A compressed archive containing the source code (source distribution).

Once build is installed, open a terminal in the project root directory and run the following command to generate the package files:

1
python -m build

This operation will create a dist/ folder in the project root, with the following contents:

1
2
3
dist/
├── module_name-module_version-py3-none-any.whl
└── module_name-module_version.tar.gz

These files can be used for installation, distribution, or uploading to the Python Package Index (PyPI).

Testing the Module

Before sharing the completed module with others, we should test it to ensure that installation and functionality work correctly.

In the project root directory, we can run the following command in editable mode, allowing us to test changes to the source code without needing to rebuild the package each time:

1
pip install -e .

If the module provides GPU-related functionality, you can install the optional dependencies using the following command:

1
pip install -e .[gpu]
Execution result reference
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
Microsoft Windows [版本 10.0.26100.6584]
(c) Microsoft Corporation. 著作權所有,並保留一切權利。

C:\Users\user\Desktop\github\autoFRK-python\autoFRK>python -m build
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
  - setuptools>=61.0
  - wheel
* Getting build dependencies for sdist...
C:\Users\user\AppData\Local\Temp\build-env-vieuwf3x\Lib\site-packages\setuptools\config\_apply_pyprojecttoml.py:82: SetuptoolsDeprecationWarning: `project.license` as a TOML table is deprecated
!!

        ********************************************************************************
        Please use a simple string containing a SPDX expression for `project.license`. You can also use `project.license-files`. (Both options available on setuptools>=77.0.0).

        By 2026-Feb-18, you need to update your project and remove deprecated calls
        or your builds will no longer be supported.

        See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.
        ********************************************************************************

!!
  corresp(dist, value, root_dir)
running egg_info
creating src\autoFRK.egg-info
writing src\autoFRK.egg-info\PKG-INFO
writing dependency_links to src\autoFRK.egg-info\dependency_links.txt
writing requirements to src\autoFRK.egg-info\requires.txt
writing top-level names to src\autoFRK.egg-info\top_level.txt
writing manifest file 'src\autoFRK.egg-info\SOURCES.txt'
reading manifest file 'src\autoFRK.egg-info\SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching '*.txt' under directory 'src\autoFRK'
warning: no files found matching '*.md' under directory 'src\autoFRK'
adding license file 'LICENSE'
writing manifest file 'src\autoFRK.egg-info\SOURCES.txt'
* Building sdist...
C:\Users\user\AppData\Local\Temp\build-env-vieuwf3x\Lib\site-packages\setuptools\config\_apply_pyprojecttoml.py:82: SetuptoolsDeprecationWarning: `project.license` as a TOML table is deprecated
!!

        ********************************************************************************
        Please use a simple string containing a SPDX expression for `project.license`. You can also use `project.license-files`. (Both options available on setuptools>=77.0.0).

        By 2026-Feb-18, you need to update your project and remove deprecated calls
        or your builds will no longer be supported.

        See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.
        ********************************************************************************

!!
  corresp(dist, value, root_dir)
running sdist
running egg_info
writing src\autoFRK.egg-info\PKG-INFO
writing dependency_links to src\autoFRK.egg-info\dependency_links.txt
writing requirements to src\autoFRK.egg-info\requires.txt
writing top-level names to src\autoFRK.egg-info\top_level.txt
reading manifest file 'src\autoFRK.egg-info\SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching '*.txt' under directory 'src\autoFRK'
warning: no files found matching '*.md' under directory 'src\autoFRK'
adding license file 'LICENSE'
writing manifest file 'src\autoFRK.egg-info\SOURCES.txt'
running check
creating autofrk-0.1.0
creating autofrk-0.1.0\src\autoFRK
creating autofrk-0.1.0\src\autoFRK.egg-info
creating autofrk-0.1.0\src\autoFRK\utils
copying files to autofrk-0.1.0...
copying LICENSE -> autofrk-0.1.0
copying MANIFEST.in -> autofrk-0.1.0
copying README.md -> autofrk-0.1.0
copying pyproject.toml -> autofrk-0.1.0
copying src\autoFRK\___init__.py -> autofrk-0.1.0\src\autoFRK
copying src\autoFRK\autoFRK.py -> autofrk-0.1.0\src\autoFRK
copying src\autoFRK\mrts.py -> autofrk-0.1.0\src\autoFRK
copying src\autoFRK.egg-info\PKG-INFO -> autofrk-0.1.0\src\autoFRK.egg-info
copying src\autoFRK.egg-info\SOURCES.txt -> autofrk-0.1.0\src\autoFRK.egg-info
copying src\autoFRK.egg-info\dependency_links.txt -> autofrk-0.1.0\src\autoFRK.egg-info
copying src\autoFRK.egg-info\requires.txt -> autofrk-0.1.0\src\autoFRK.egg-info
copying src\autoFRK.egg-info\top_level.txt -> autofrk-0.1.0\src\autoFRK.egg-info
copying src\autoFRK\utils\___init__.py -> autofrk-0.1.0\src\autoFRK\utils
copying src\autoFRK\utils\device.py -> autofrk-0.1.0\src\autoFRK\utils
copying src\autoFRK\utils\logger.py -> autofrk-0.1.0\src\autoFRK\utils
copying src\autoFRK\utils\predictor.py -> autofrk-0.1.0\src\autoFRK\utils
copying src\autoFRK\utils\utils.py -> autofrk-0.1.0\src\autoFRK\utils
copying src\autoFRK.egg-info\SOURCES.txt -> autofrk-0.1.0\src\autoFRK.egg-info
Writing autofrk-0.1.0\setup.cfg
Creating tar archive
removing 'autofrk-0.1.0' (and everything under it)
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
  - setuptools>=61.0
  - wheel
* Getting build dependencies for wheel...
C:\Users\user\AppData\Local\Temp\build-env-exul05dl\Lib\site-packages\setuptools\config\_apply_pyprojecttoml.py:82: SetuptoolsDeprecationWarning: `project.license` as a TOML table is deprecated
!!

        ********************************************************************************
        Please use a simple string containing a SPDX expression for `project.license`. You can also use `project.license-files`. (Both options available on setuptools>=77.0.0).

        By 2026-Feb-18, you need to update your project and remove deprecated calls
        or your builds will no longer be supported.

        See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.
        ********************************************************************************

!!
  corresp(dist, value, root_dir)
running egg_info
writing src\autoFRK.egg-info\PKG-INFO
writing dependency_links to src\autoFRK.egg-info\dependency_links.txt
writing requirements to src\autoFRK.egg-info\requires.txt
writing top-level names to src\autoFRK.egg-info\top_level.txt
reading manifest file 'src\autoFRK.egg-info\SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching '*.txt' under directory 'src\autoFRK'
warning: no files found matching '*.md' under directory 'src\autoFRK'
adding license file 'LICENSE'
writing manifest file 'src\autoFRK.egg-info\SOURCES.txt'
* Building wheel...
C:\Users\user\AppData\Local\Temp\build-env-exul05dl\Lib\site-packages\setuptools\config\_apply_pyprojecttoml.py:82: SetuptoolsDeprecationWarning: `project.license` as a TOML table is deprecated
!!

        ********************************************************************************
        Please use a simple string containing a SPDX expression for `project.license`. You can also use `project.license-files`. (Both options available on setuptools>=77.0.0).

        By 2026-Feb-18, you need to update your project and remove deprecated calls
        or your builds will no longer be supported.

        See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.
        ********************************************************************************

!!
  corresp(dist, value, root_dir)
running bdist_wheel
running build
running build_py
creating build\lib\autoFRK
copying src\autoFRK\autoFRK.py -> build\lib\autoFRK
copying src\autoFRK\mrts.py -> build\lib\autoFRK
copying src\autoFRK\___init__.py -> build\lib\autoFRK
creating build\lib\autoFRK\utils
copying src\autoFRK\utils\device.py -> build\lib\autoFRK\utils
copying src\autoFRK\utils\logger.py -> build\lib\autoFRK\utils
copying src\autoFRK\utils\predictor.py -> build\lib\autoFRK\utils
copying src\autoFRK\utils\utils.py -> build\lib\autoFRK\utils
copying src\autoFRK\utils\___init__.py -> build\lib\autoFRK\utils
running egg_info
writing src\autoFRK.egg-info\PKG-INFO
writing dependency_links to src\autoFRK.egg-info\dependency_links.txt
writing requirements to src\autoFRK.egg-info\requires.txt
writing top-level names to src\autoFRK.egg-info\top_level.txt
reading manifest file 'src\autoFRK.egg-info\SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching '*.txt' under directory 'src\autoFRK'
warning: no files found matching '*.md' under directory 'src\autoFRK'
adding license file 'LICENSE'
writing manifest file 'src\autoFRK.egg-info\SOURCES.txt'
installing to build\bdist.win-amd64\wheel
running install
running install_lib
creating build\bdist.win-amd64\wheel
creating build\bdist.win-amd64\wheel\autoFRK
copying build\lib\autoFRK\autoFRK.py -> build\bdist.win-amd64\wheel\.\autoFRK
copying build\lib\autoFRK\mrts.py -> build\bdist.win-amd64\wheel\.\autoFRK
creating build\bdist.win-amd64\wheel\autoFRK\utils
copying build\lib\autoFRK\utils\device.py -> build\bdist.win-amd64\wheel\.\autoFRK\utils
copying build\lib\autoFRK\utils\logger.py -> build\bdist.win-amd64\wheel\.\autoFRK\utils
copying build\lib\autoFRK\utils\predictor.py -> build\bdist.win-amd64\wheel\.\autoFRK\utils
copying build\lib\autoFRK\utils\utils.py -> build\bdist.win-amd64\wheel\.\autoFRK\utils
copying build\lib\autoFRK\utils\___init__.py -> build\bdist.win-amd64\wheel\.\autoFRK\utils
copying build\lib\autoFRK\___init__.py -> build\bdist.win-amd64\wheel\.\autoFRK
running install_egg_info
Copying src\autoFRK.egg-info to build\bdist.win-amd64\wheel\.\autoFRK-0.1.0-py3.12.egg-info
running install_scripts
creating build\bdist.win-amd64\wheel\autofrk-0.1.0.dist-info\WHEEL
creating 'C:\Users\user\Desktop\github\autoFRK-python\autoFRK\dist\.tmp-mxuu22cz\autofrk-0.1.0-py3-none-any.whl' and adding 'build\bdist.win-amd64\wheel' to it
adding 'autoFRK/___init__.py'
adding 'autoFRK/autoFRK.py'
adding 'autoFRK/mrts.py'
adding 'autoFRK/utils/___init__.py'
adding 'autoFRK/utils/device.py'
adding 'autoFRK/utils/logger.py'
adding 'autoFRK/utils/predictor.py'
adding 'autoFRK/utils/utils.py'
adding 'autofrk-0.1.0.dist-info/licenses/LICENSE'
adding 'autofrk-0.1.0.dist-info/METADATA'
adding 'autofrk-0.1.0.dist-info/WHEEL'
adding 'autofrk-0.1.0.dist-info/top_level.txt'
adding 'autofrk-0.1.0.dist-info/RECORD'
removing build\bdist.win-amd64\wheel
Successfully built autofrk-0.1.0.tar.gz and autofrk-0.1.0-py3-none-any.whl

C:\Users\user\Desktop\github\autoFRK-python\autoFRK>

Uploading the Module

We can use the twine tool to upload the packaged files we just created to PyPI (Python Package Index), allowing others to install the module via pip install.

PyPI is the default source for pip, where modules can be downloaded and installed.

1
pip install module_name

First, install the upload tool twine:

1
pip install --upgrade twine

TestPyPI

PyPI provides a testing environment, so we can choose to first upload and test the package on TestPyPI before publishing it to the official PyPI.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/TestPyPI/1.png
TestPyPI account registration page.

If this is your first time using TestPyPI, register a TestPyPI account and check your email for the verification message.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/TestPyPI/2.png
Confirming the account.

For first-time users, upon returning to TestPyPI, save the recovery code for account access in case of loss.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/TestPyPI/3.png
Enter a saved recovery code to confirm.

Next, enable 2FA verification to proceed with other operations.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/TestPyPI/4.png
2FA verification.

Now, in Account settings under API tokens, generate a token to allow terminal uploads of the module.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/TestPyPI/5.png
Manage tokens.
https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/TestPyPI/6.png
Create a token.

The token is displayed only once. After obtaining it, create the file C:\Users\your_username\.pypirc and enter the following content to save the token:

1
2
3
[testpypi]
  username = __token__
  password = your_token

Next, reopen a terminal in the project directory and run the following command to upload the module to TestPyPI:

1
twine upload --repository testpypi dist/*

The expected output should look like this:

Execution result reference
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
PS C:\Users\user\Desktop\github\autoFRK-python> twine upload --repository testpypi dist/*
>> 
Uploading distributions to https://test.pypi.org/legacy/
Uploading autofrk-0.1.0-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.8/100.8 kB • 00:00 • 1.1 MB/s        
Uploading autofrk-0.1.0.tar.gz
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 110.3/110.3 kB • 00:00 • ?

View at:
https://test.pypi.org/project/autoFRK/0.1.0/

After returning to TestPyPI, you can see the newly uploaded module in the Your projects section.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/TestPyPI/7.png
Recently uploaded module.

Once the upload is confirmed, you can test the installation using the following command. The --extra-index-url https://pypi.org/simple flag ensures that any required dependencies from the official PyPI are also found.

1
pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple module_name

PyPI

Once everything is ready, you can officially upload the module to PyPI. The detailed upload process is documented below.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/1.png
PyPI account registration page.

First, if this is your first time using PyPI, create an account. Follow the registration steps and check your email for the verification message, just like with TestPyPI.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/2.png
Confirming the account.

After returning to PyPI, save the recovery code as a precaution.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/3.png
Save the recovery code.

Enter your password to continue.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/4.png
Enter password.

It is recommended to click Save to store the recovery code locally.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/5.png
Save the recovery code.

Next, enter a saved recovery code to confirm your account.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/6.png
Enter a saved recovery code to confirm.

As with TestPyPI, 2FA verification must be enabled to proceed.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/7.png
Enable 2FA verification.

At this point, account registration is complete.

Now, just like with TestPyPI, you need to create a token to upload the module from your local machine.

First, go to Account settings.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/8.png
Account settings.

Locate the API tokens section and click Add API token.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/9.png
API tokens.

Provide a name for the token for future reference.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/10.png
Token name.

Select the Scope (should be Entire account) and click Create token.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/11.png
Select scope and create token.

Copy the token displayed in the red box below; it will be used later.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/12.png
PyPI token.

On your local machine, create a file .pypirc in C:\Users\your_username\ and enter the following content to store the token. If you previously used TestPyPI, its token will also be saved here.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/13.png
Save PyPI token.

Finally, open a terminal in the project directory and run the following command to upload the module to PyPI:

1
twine upload dist/*

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/14.png
Uploading the module.

The expected output should look like this:

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/15.png
Upload successful.

Note
PyPI and TestPyPI do not accept modules with the same version number. Remember to update the module version in pyproject.toml for each upload.

After returning to PyPI, you can see the newly uploaded module in the Your projects section.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/16.png
Recently uploaded module.

You can also click View to see the module’s page, which will display the README content you wrote earlier.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/Notes%20on%20Creating%20Python%20Modules/PyPI/17.png
Module page.

Once the upload is confirmed, you can install the module using the following command:

1
pip install module_name

Conclusion

Creating a custom Python module is an exciting experience. Sharing a module that I built myself gives me a strong sense of accomplishment. During the development of this module, I conducted extensive testing and realized the importance of maintaining a well-structured README and organizing the module files systematically. In the future, I hope to further refine this module, continuously optimizing its functionality and user interface to maximize its practical benefits.

Environment

  • Operating System: Windows 11 24H2
  • Programming Language: Python 3.12.8

See also

Warning
The last update time of this article is October 22, 2025, and the content may be outdated. Please be aware. If you notice any errors or broken images, feel free to leave a comment for corrections.