Skip to content

Commit b120354

Browse files
bunch of small improvements
1 parent 4a2f56a commit b120354

File tree

5 files changed

+34
-22
lines changed

5 files changed

+34
-22
lines changed

_quarto.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ project:
44
preview:
55
port: 4000
66

7-
website:
8-
google-analytics: "G-XXXXXXXX"
9-
107
book:
118
title: "Python Packaging Essentials"
129
subtitle: "Practical guide that walks you through creating, organizing, testing, and publishing Python packages."
@@ -42,7 +39,8 @@ format:
4239
number-depth: 1
4340
css: styles.css
4441
theme:
45-
- cosmo
46-
- brand
42+
light:
43+
- cosmo
44+
- brand
4745
pdf:
4846
documentclass: scrreprt

blog/create-a-package/handling-dependencies.qmd

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: "Handling dependencies"
44

55
Dependencies are the external Python packages your code needs in order to work, such as `requests`, `numpy`, or `pandas`.
66

7-
Here we'll focus on using [uv](https://docs.astral.sh/uv/){target="_blank"} to handle dependencies, as it's currently the best tool for this out there (it's fast and fairly easy to use, especially if you know `pip`).
7+
Here we'll focus on using [uv](https://docs.astral.sh/uv/){target="_blank"} to handle dependencies, as it's currently the best tool for this out there (it's fast and fairly easy to use, especially if you know `pip` or other package manager like `Cargo`.).
88

99
## Specify dependencies
1010

@@ -21,7 +21,7 @@ def normalize(array):
2121

2222
When people want to use our function, they **need** to have `numpy` installed for it to work, otherwise it will raise a `ModuleNotFoundError` on their machine.
2323

24-
So in order to ensure is `numpy` installed, we set `numpy` as a dependency of our package. This means that every time someone install our package, they will also install `numpy`.
24+
So in order to ensure that this does not happen, we need to tell Python to also install `numpy` when installing our package. In practice, we say that we set `numpy` as a **dependency** of our package. This means that every time someone install our package, they will also install `numpy`.
2525

2626
The dependencies of a package are listed in the `pyproject.toml` file. If you don't know what that is, check out [organizing a package](./organize-a-package.html).
2727

@@ -63,7 +63,7 @@ dependencies = [
6363

6464
:::
6565

66-
If we add other dependencies, they will be added to the `dependencies` list.
66+
From now on, numpy will also be installed when users install our package. If we add other dependencies, they will be added to the "dependencies" list: we can have as many dependencies as we want, but we want to avoid that.
6767

6868
## Avoiding dependencies
6969

@@ -125,7 +125,7 @@ uv pip install numpy
125125

126126
:::
127127

128-
Note that for each of those, the package resolver will always try to install the latest version it can depending on the other dependencies. If a package requires `numpy<=2.1.0`, other packages **must** include `numpy` `2.1.0` for it to work.
128+
Note that for each of those, the package manager will always try to install the latest version it can depending on the other dependencies. If a package requires `numpy<=2.1.0`, other packages **must** include `numpy` `2.1.0` for it to work.
129129

130130
At this point, you might ask, **how do I know** which versions of each dependencies are required for my package? Well, as far as I know, there is no easy answer to this, but there are ways to ensure you don't get unexpected behaviors.
131131

@@ -146,7 +146,7 @@ You have to test that your code works as expected on those versions. The best wa
146146

147147
### ... and be convenient
148148

149-
> Dependening on whether you're planning on distributing your package (e.g., put it on PyPI and allow other people to install it) or not, you might want to do different things here. We'll assume you want to distribute it at the end.
149+
> Depending on whether you're planning on distributing your package (e.g., put it on PyPI and allow other people to install it) or not, you might want to do different things here. We'll assume you want to distribute it at the end.
150150
151151
When your package goaled is to be installed by other people, you want to be convenient. By that I mean **not being too restrictive**.
152152

@@ -250,6 +250,10 @@ def read_file(filename):
250250
)
251251
```
252252

253+
::: {.callout-important}
254+
It is **required** here to place `import` inside the function, because otherwise a `ModuleNotFoundError` error will be generated on the user's machine, even when importing a function with only optional dependencies..
255+
:::
256+
253257
This will give your users a **clear and meaningful error message** that they can resolve very quickly. This kind of thing exist for the same reason we're talking about in this article: trying to minimize the number of dependencies (especially the unused ones!).
254258

255259
In order to add package to your optional dependencies, you can run:
@@ -319,3 +323,4 @@ uv sync --all-groups
319323
```
320324

321325

326+
The next section is about [code quality](../code-quality/) in your package.

blog/create-a-package/introduction-to-packaging.qmd

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ from my_package import something
2424

2525
Without it, Python treats the directory `my_package/` as a regular directory, **not something it can import from**.
2626

27+
> Note that this is not always true, but it does not matter here. See [namespace packages](https://packaging.python.org/en/latest/guides/packaging-namespace-packages/){target="_blank"}.
28+
2729

2830

2931
## Package vs Module vs Library
@@ -34,7 +36,7 @@ These terms get thrown around a lot. Here's the quick breakdown:
3436
- **Package**: A directory with an `__init__.py`, possibly containing multiple modules (e.g., multiple files)
3537
- **Library**: A more general programming term and refers to a bundle of code that can be used ([source](https://www.reddit.com/r/Python/comments/weycrc/comment/iiqy6fi/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button){target="_blank"})
3638

37-
Library and package most of the time refer to the same thing. All packages are libraries, the opposite is not true. For the sake of simplicity, it's ok to consider them "equivalent", even though we're are mostly interested in packages in practice.
39+
Library and package most of the time refer to the same thing. All packages are libraries, the opposite is not true. For the sake of simplicity, **it's ok to consider them "equivalent"**, even though we're are mostly interested in packages in practice.
3840

3941
Now, let's create the smallest Python package possible.
4042

@@ -105,6 +107,8 @@ def say_hello(name):
105107
print(message)
106108
```
107109

110+
Here we define the main function of our package.
111+
108112
### `__init__.py`
109113

110114
```{.python filename="package_name/__init__.py"}
@@ -113,13 +117,14 @@ from .my_module import say_hello
113117
__all__ = ["say_hello"]
114118
```
115119

120+
Without the `__init__.py` file, we wouldn't be able to use `from my_package import say_hello` and would have to use `from my_package.my_module import say_hello`, which isn't the best syntax (but sometimes it can be!).
116121
:::
117122

118123
## Use our package
119124

120-
Now, how do we use our package, from a user point of view?
125+
Untile now, we were from the package developer side, but how do we use our package, from a user point of view?
121126

122-
For this, you'll need to have [uv](https://docs.astral.sh/uv/){target="_blank"} installed.
127+
For this, you'll need to have [uv](https://docs.astral.sh/uv/){target="_blank"} installed (not mandatory, but it makes things much easier).
123128

124129
Then we'll need to run a command in our terminal at `Desktop/my_package/`:
125130

@@ -144,4 +149,6 @@ say_hello("Julia")
144149
> Hello Julia
145150
146151

147-
And now we have a fully functional Python package! This is just the beginning, but this is an important fondation to have for what's coming next.
152+
And now we have a **fully functional Python package**! This is just the beginning, but this is an important fondation to have for what's coming next.
153+
154+
Next, we need to [organize the package](./organize-a-package.html), particularly to get an overview of all the files we need (most of which are not Python files).

blog/create-a-package/organize-a-package.qmd

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ __all__ = ["count_sunflowers"]
3838

3939
## Add main Python project files
4040

41-
Next, we need to create a few essential files at the root of the project.
41+
Next, we need to create a few essential files at the root of the project. As you can see, most of them are not Python files, but they are still very important.
4242

4343
::: {.panel-tabset}
4444

4545
### `pyproject.toml`
4646

47-
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.
47+
All the metadata for the package. This is essentially your package's identifier, which contains a lot of useful information when we want to distribute a package so that anyone can easily install it.
48+
49+
For example, it contains information about the license (what are users allowed to do with our package?), dependencies (what packages does our package need?), and lots of other metadata about the package (author(s), version, description, etc.).
4850

4951
Here is a simple version of this file:
5052

@@ -207,7 +209,7 @@ sunflower/
207209

208210
## Add an internal function
209211

210-
When creating a package, it is very practical to create functions that we will use internally: inside the package itself.
212+
When creating a package, it is very useful to create functions that we will use internally: within the package itself, but which are not intended for users of the package.
211213

212214
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`.
213215

@@ -263,3 +265,7 @@ sunflower/
263265
├── sandbox.py
264266
└── pyproject.toml
265267
```
268+
269+
We now have a basic but fairly robust file organization in our package. Now, if we want to continue improving our package, everything will happen directly in the `sunflower/sunflower` directory.
270+
271+
The next step is to [handle dependencies](./handling-dependencies.html), i.e., identify and specify the packages needed to use our package.

styles.css

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,8 @@ a:not(.sidebar a):not(header a):not(.nav-item a):not(.nav-page a)::after {
3838
left: 0;
3939
bottom: 0;
4040
width: 100%;
41-
height: 3px;
41+
height: 2px;
4242
background-color: var(--primary-color);
4343
transition: all 0.3s ease;
4444
z-index: -1; /* goes behind text */
4545
}
46-
47-
a:not(.sidebar a):not(header a):not(.nav-item a):not(.nav-page a):hover::after {
48-
height: 80%; /* increased thickness */
49-
}

0 commit comments

Comments
 (0)