2 Organize a package
In order to follow the steps below, you’ll need to have both Git and uv installed on your machine. Both are command-line tools, meaning you’ll use your terminal to run commands that perform various actions.
Let’s assume we’re naming our Python package “sunflower”, with a single function:
sunflower/
└── sunflower/
├── __init__.py └── my_module.py
To learn about this structure and why we need a __init__.py
file, check out the previous blog post.
We’ll also assume that my_module.py
looks like this:
import re
def count_sunflowers(s):
= re.sub(r"[^a-zA-Z\s]", "", s) # Remove non-text characters
s = s.lower() # Convert to lowercase
s = s.split().count("sunflower")
n_sunflower = s.split().count("sunflowers")
n_sunflowers return n_sunflower + n_sunflowers
The __init__.py
file look like this:
from .my_module import count_sunflowers
__all__ = ["count_sunflowers"]
2.1 Add main Python project files
Next, we need to create a few essential files at the root of the project.
All the package metadata. It will contain a lot of useful information when we want to distribute this PyPI package so that everyone can install it easily. Don’t worry too much about all of its content.
Here is a simple version of this file:
[project]
name = "sunflower"
description = "Create pretty sunflowers"
version = "0.1.0"
license = "MIT"
license-files = ["LICENSE"]
keywords = ["sunflower", "flower"]
authors = [
{ name="your_name", email="your_name@mail.com" },
]
readme = "README.md"
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"Development Status :: 3 - Alpha"
]
dependencies = []
[build-system]
requires = [
"setuptools",
"setuptools-scm",
]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["sunflower"]
[tool.uv.sources]
sunflower = { workspace = true }
[project.urls]
Homepage = "https://your_name.github.io/sunflower/"
Issues = "https://github.com/your_name/sunflower/issues"
Documentation = "https://your_name.github.io/sunflower/"
Repository = "https://github.com/your_name/sunflower"
A basic text file containing the licence for your package. This licence is important because it tells other people what they are allowed to do with your package.
It is specific to each project, but you can find out more at choosealicence.com.
Here is an example of the most common licence: the MIT licence.
Copyright (c) 2025 Your Name
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This is a directory used internally by the Git software to track all changes in the project. Assuming that the first “sunflower” directory is in your Desktop/
directory, you should create this directory by running the git init
command when you are in the Desktop/sunflower/
directory.
It’s very likely that you won’t see it, as most operating systems (Windows, MacOS, etc.) hide files/directories that start with .
, but it doesn’t matter. This directory will be managed entirely by Git itself, so we recommend that you never make any manual changes to it.
A file in which each line describes one or more files/directories that are not explicitly part of the project or are not relevant in general. Don’t worry too much about this, you can just start with the example content below.
It is very likely that you will not see it outside your code editor, as most operating systems (Windows, MacOS, etc.) hide files/directories that start with .
, but that doesn’t matter.
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv/
venv/
.env/
env/
# VS code config
.vscode/
# files on mac
.DS_Store
# all cache files
*cache*
# Sandbox files
sandbox.py sandbox.ipynb
A directory containing all the things we need to work properly in our Python environment. It contains a Python interpreter, all the packages used in the project (e.g. numpy
, requests
, etc), and a few other things.
The best way to create one is to run uv venv
.
It is very likely that you will not see it outside your code editor, as most operating systems (Windows, MacOS, etc.) hide files/directories that start with .
, but that doesn’t matter.
A markdown file that describes the project, gives advice on how to use it, install it and so on. There are no rules about what to do with this file, it’s just used to tell people what is the first thing they should read before using your package.
For example, it could be something like this:
# sunflower: my cool Python package
`sunflower` project.
Welcome to the homepage of the
It's a new project, but it will be available soon!
A file that we will use to test and use our package. It’s optional but practical.
The organisation of our project now looks like this:
sunflower/
├── sunflower/
│ ├── __init__.py
│ └── my_module.py
├── .git/
├── .venv/
├── .gitignore
├── README.md
├── LICENSE
├── sandbox.py └── pyproject.toml
2.2 Add an internal function
When creating a package, it is very practical to create functions that we will use internally: inside the package itself.
If we go back to our previous example, we might want to have a separate function that takes a string and cleans it up by removing non-text characters and putting it in lower case. Let’s name this function _clean_string()
and place it in a new file: other_module.py
.
import re
def _clean_string(s):
= re.sub(r"[^a-zA-Z\s]", "", s) # Remove non-text characters
s = s.lower() # Convert to lowercase
s return s
Our code in my_module.py
should now become:
from .other_module import _clean_string
def count_sunflowers(s):
= _clean_string(s)
s = s.split().count("sunflower")
n_sunflower = s.split().count("sunflowers")
n_sunflowers return n_sunflower + n_sunflowers
We now have 2 functions:
count_sunflowers()
a public function that users of the package will use._clean_string()
a private function used internally. The underscore (‘_
’) at the beginning of the function name tells other people that it should not be used outside the package from which it came.
Note that _clean_string()
is still usable by users if they run it:
from sunflower.other_module import _clean_string
But as you can see from the documentation blog post, we won’t have or create documentation on these functions, so they’re unlikely to find it anyway.
2.3 Final organization
After all these steps, our package now looks like this:
sunflower/
├── sunflower/
│ ├── __init__.py
│ ├── my_module.py
│ └── other_module.py
├── .git/
├── .venv/
├── .gitignore
├── README.md
├── LICENSE
├── sandbox.py └── pyproject.toml