How to Create a Python Package
What you’ll build or solve
You’ll turn a folder of Python files into an importable package with a clean structure and one reliable entry point.
When this approach works best
This approach works best when you:
Learn Python on Mimo
- Split code into multiple files and want imports that keep working as the project grows.
- Plan to reuse your code across scripts, projects, or teammates.
- Want a clean “public API” so others import from one place, not random files.
Avoid this approach when:
- You have a single small script and no plans to reuse it. A package structure will slow you down.
Prerequisites
- Python 3 installed
- You know what a file and folder are
- You know basic imports like
import osandfrom x import y - You can run terminal commands
Step-by-step instructions
1) Create a project folder with a src layout
A src/ layout prevents accidental imports from your working directory during development. Without it, Python can sometimes import your package “by coincidence” because the current folder is on sys.path, and that can hide packaging problems until later.
Project structure:
CSS
my_project/
src/
my_package/
__init__.py
utils.py
pyproject.toml
Create the folders and files:
Bash
mkdir-p my_project/src/my_package
cd my_project
touch src/my_package/__init__.py src/my_package/utils.py pyproject.toml
2) Add some code inside the package
Put reusable code inside your package folder.
src/my_package/utils.py:
defslugify(text:str) ->str:
text=text.strip().lower()
returntext.replace(" ","-")
3) Decide what your package exports in __init__.py
Use __init__.py to define what people should import from your package.
src/my_package/__init__.py:
CSS
from.utilsimportslugify
__all__= ["slugify"]
What to look for:
Re-exporting names in __init__.py lets users write from my_package import slugify instead of reaching into internal modules.
4) Add a minimal pyproject.toml
This file tells packaging tools how to build and install your project.
setuptoolsis the build tool that turns your package into an installable project.nameis the install name shown inpip. It often uses hyphens.- Your folder on disk usually uses underscores because Python imports use underscores.
dependencieslists packages your code needs at runtime (likerequests).
pyproject.toml:
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "0.1.0"
description = "Example package"
requires-python = ">=3.9"
dependencies = [
"requests>=2.28.0",
]
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]
What to look for:
- Use hyphens in
[project].name(pip install my-package), and underscores in your folder name (src/my_package) and imports (import my_package). - Change
requires-pythonif your code needs newer Python features. - Keep
dependenciesaligned with what you import in your package.
5) Install your package in editable mode
Editable install lets you import the package while you keep editing the code.
python-m pip install-e .
What to look for:
Running pip as python -m pip helps you install into the same Python that runs your scripts.
6) Test imports from a separate script
Create a script outside the package folder.
test_import.py (in my_project/):
Bash
frommy_packageimportslugify
print(slugify("Hello Package"))
Run it from the project root:
python test_import.py
7) Make the package runnable with python -m
This is useful when your package has a “main” action, like a small CLI or a quick demo. Python looks for my_package/__main__.py when you run python -m my_package.
Add:
Bash
src/my_package/__main__.py
src/my_package/__main__.py:
frommy_packageimportslugify
defmain() ->None:
print(slugify("Run with -m"))
if__name__=="__main__":
main()
Run it:
python-m my_package
What to look for:
Running with -m uses the package context, so imports behave more like they will in real usage than when you run internal files directly.
Examples you can copy
1) Small utility package
CSS
my_project/
src/
text_tools/
__init__.py
clean.py
pyproject.toml
src/text_tools/clean.py:
defnormalize_spaces(s:str) ->str:
return" ".join(s.split())
src/text_tools/__init__.py:
CSS
from.cleanimportnormalize_spaces
__all__= ["normalize_spaces"]
2) When to keep it flat vs nest subpackages
Keep it flat when you have a few modules and one clear theme:
Bash
src/my_package/
__init__.py
utils.py
validators.py
formatters.py
Nest into subpackages when you have distinct areas that would become clutter in one folder, or when modules naturally group together:
src/my_app/
__init__.py
api/
__init__.py
client.py
core/
__init__.py
config.py
A simple rule: if you have more than ~6–8 modules in one folder, or you often say “this part is API code” vs “this part is core logic,” nesting usually helps.
3) Public API pattern for cleaner imports
Goal: allow from my_package import slugify instead of from my_package.utils import slugify.
src/my_package/__init__.py:
CSS
from.utilsimportslugify
__all__= ["slugify"]
User code:
Bash
frommy_packageimportslugify
print(slugify("Clean Imports"))
Common mistakes and how to fix them
Mistake 1: Running a module file directly and breaking imports
You might run:
Bash
python src/my_package/utils.py
Why it breaks:
Running a file inside a package as a script can change how Python resolves imports, so relative imports and package context can break.
Correct approach:
Run from the project root using -m or a separate script:
python-m my_package
Or:
python test_import.py
Mistake 2: Mixing up the install name and the import name
You might set name = "my_package" in pyproject.toml, then try:
importmy-package
Why it breaks:
The install name (often hyphenated) is for pip, while the import name must be a valid Python identifier (underscores, no hyphens).
Correct approach:
- Use hyphens in
pyproject.toml:
name = "my-package"
- Use underscores in folders and imports:
Bash
src/my_package/
importmy_package
Mistake 3: Installing but still seeing ModuleNotFoundError
You might run:
pip install-e .
python test_import.py
Why it breaks:
pip might install into a different Python than the one running your script.
Correct approach:
python-m pip install-e .
python test_import.py
Troubleshooting
If you see ModuleNotFoundError: No module named 'my_package', confirm you ran python -m pip install -e . from the project root, then run your script again from that same root.
If you see ImportError: attempted relative import with no known parent package, avoid running files inside the package directly. Use python -m my_package or run a top-level script.
If you see No module named pip, run:
CSS
python-m ensurepip--upgrade
If your editor can’t find imports but the code runs, select the correct interpreter in your editor, then restart the language server.
Quick recap
- Use a
src/layout to avoid accidental imports during development. - Put code under
src/<package_name>/. - Use underscores for import/package folders, hyphens for the install name in
pyproject.toml. - Add dependencies in
pyproject.tomlwhen your code imports other packages. - Install in editable mode with
python -m pip install -e .. - Run with
python -m my_packageor a script from the project root.
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot