Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
on:
workflow_dispatch:
pull_request:
branches: [main]

name: Quarto Check

jobs:
build-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Enable caching
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install the project
run: uv sync --all-groups

- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2

- name: Install TinyTex
run: quarto install tinytex

- name: Configure Git identity
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

- name: Render and Publish
run: |
source .venv/bin/activate
quarto check
quarto render
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ _book/
.DS_Store
/.quarto/
docs/
.venv/
.venv/
**/*.quarto_ipynb
5 changes: 4 additions & 1 deletion _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ book:
- blog/workflow/github-actions.qmd
- blog/workflow/pre-commit.qmd
- blog/workflow/publish-to-pypi.qmd
- blog/bonus/index.qmd
- part: blog/bonus/index.qmd
chapters:
- blog/bonus/miscellaneous.qmd
- blog/bonus/makefile.qmd
- python-package-template.qmd
- contributing.qmd

Expand Down
89 changes: 1 addition & 88 deletions blog/bonus/index.qmd
Original file line number Diff line number Diff line change
@@ -1,90 +1,3 @@
---
title: Bonus
---

## How to name your package

Yes, creating a good Python package name is both an art and a bit of a science. There **are constraints** you should follow, and some **best practices** that can help your package stand out and be easy to use.

### Constraints

- **Lowercase only**: Package names should be all lowercase.
- **No special characters or spaces**: Use only letters, numbers, and underscores or dashes (`a-z`, `0-9`, `_`, `-`).
- **Can't conflict with standard library modules**: Avoid names like `json`, `os`, `email`, etc.
- **Must be unique on PyPI**: Check if the name is available: [https://pypi.org/](https://pypi.org/)
- **Max length**: There’s no strict limit, but practical limits (about 50 characters) make sense.
- **Underscores vs Dashes**:
- Use dashes (`-`) in the **distribution name** (`setup.py` or `pyproject.toml`).
- Use underscores (`_`) or no separator at all in **importable module names**.

### Best Practices

- **Short & memorable**: Easier for users to type and remember.
- **Descriptive but concise**: Reflect what the package does.
* Good: `requests`, `black`, `httpx`
* Bad: `jdhfhc`, `my_cool_package`, `python_toolkit_2023_version_final`
- **Avoid generic terms** unless paired cleverly: `data`, `utils`, `tools`, etc.
- **Avoid abbreviations** unless well-known.
- **Check for conflicts**: Google the name and check on GitHub too, not just PyPI.
- **Consider branding**: If it becomes popular, the name matters.


## How to name files

Having good filenames is mostly useful for having a clear and consistent project architecture. There are some best practices to follow:

- use lowercase only
- avoid spaces and odd characters
- keep it short
- use underscores "`_`"

::: {.panel-tabset}

### Bad file names

```txt
my file.py
Myfile.py
myFile.py
my@file.py
my-file.py
this-file-does-this-and-that.py
```

### Good file names

```txt
my_file.py
myfile.py
```

:::


## What is the `__all__` variable

The `__all__` variable in Python lives in the `__init__.py` file and is used to control **what gets imported** when someone uses `from my_package import *`. It should be defined at the module level as a list of strings, where each string is the name of a symbol—like a function, class, or variable—that you want to make publicly available.

If `__all__` is present, only those names listed will be imported during wildcard imports. If it's not defined, Python will import all names that don’t start with an underscore by default.

For example, consider a file called `my_module.py`:

```{.python filename="my_package/my_module.py"}
def cool_function():
return "This is public"

class CoolClass:
pass
```

And our `__init__.py` file:

```{.python filename="my_package/__init__.py"}
from .my_module import cool_function, CoolClass

__all__ = ["cool_function"]
```

Now if a user does `from my_package import *`, only `cool_function` will be available. Attempting to use `CoolClass` will raise a `NameError`.

It's also important to have in mind that, most of the time, it's highly discouraged to use `from my_package import *` as it does not explicit what is actually imported. Interestingly, it's even [not allowed in marimo notebooks](https://www.youtube.com/watch?v=8ZPIkDInKRM&ab_channel=marimo){target="_blank"}.
---
3 changes: 3 additions & 0 deletions blog/bonus/makefile.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
title: "Makefile"
---
90 changes: 90 additions & 0 deletions blog/bonus/miscellaneous.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: "Miscellaneous"
---

## How to name your package

Yes, creating a good Python package name is both an art and a bit of a science. There **are constraints** you should follow, and some **best practices** that can help your package stand out and be easy to use.

### Constraints

- **Lowercase only**: Package names should be all lowercase.
- **No special characters or spaces**: Use only letters, numbers, and underscores or dashes (`a-z`, `0-9`, `_`, `-`).
- **Can't conflict with standard library modules**: Avoid names like `json`, `os`, `email`, etc.
- **Must be unique on PyPI**: Check if the name is available: [https://pypi.org/](https://pypi.org/)
- **Max length**: There’s no strict limit, but practical limits (about 50 characters) make sense.
- **Underscores vs Dashes**:
- Use dashes (`-`) in the **distribution name** (`setup.py` or `pyproject.toml`).
- Use underscores (`_`) or no separator at all in **importable module names**.

### Best Practices

- **Short & memorable**: Easier for users to type and remember.
- **Descriptive but concise**: Reflect what the package does.
* Good: `requests`, `black`, `httpx`
* Bad: `jdhfhc`, `my_cool_package`, `python_toolkit_2023_version_final`
- **Avoid generic terms** unless paired cleverly: `data`, `utils`, `tools`, etc.
- **Avoid abbreviations** unless well-known.
- **Check for conflicts**: Google the name and check on GitHub too, not just PyPI.
- **Consider branding**: If it becomes popular, the name matters.


## How to name files

Having good filenames is mostly useful for having a clear and consistent project architecture. There are some best practices to follow:

- use lowercase only
- avoid spaces and odd characters
- keep it short
- use underscores "`_`"

::: {.panel-tabset}

### Bad file names

```txt
my file.py
Myfile.py
myFile.py
my@file.py
my-file.py
this-file-does-this-and-that.py
```

### Good file names

```txt
my_file.py
myfile.py
```

:::


## What is the `__all__` variable

The `__all__` variable in Python lives in the `__init__.py` file and is used to control **what gets imported** when someone uses `from my_package import *`. It should be defined at the module level as a list of strings, where each string is the name of a symbol—like a function, class, or variable—that you want to make publicly available.

If `__all__` is present, only those names listed will be imported during wildcard imports. If it's not defined, Python will import all names that don’t start with an underscore by default.

For example, consider a file called `my_module.py`:

```{.python filename="my_package/my_module.py"}
def cool_function():
return "This is public"

class CoolClass:
pass
```

And our `__init__.py` file:

```{.python filename="my_package/__init__.py"}
from .my_module import cool_function, CoolClass

__all__ = ["cool_function"]
```

Now if a user does `from my_package import *`, only `cool_function` will be available. Attempting to use `CoolClass` will raise a `NameError`.

It's also important to have in mind that, most of the time, it's highly discouraged to use `from my_package import *` as it does not explicit what is actually imported. Interestingly, it's even [not allowed in marimo notebooks](https://www.youtube.com/watch?v=8ZPIkDInKRM&ab_channel=marimo){target="_blank"}.
51 changes: 51 additions & 0 deletions blog/code-quality/writing-documentation.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,59 @@ We do not have to rewrite the name of the arguments and so on thanks to this. Th
Reference documentation is a very important part of documentation, but it is not exhaustive. It is important that you also add tutorials, guides, and explanations, as suggested in the [Diátaxis framework](https://diataxis.fr/){target="_blank"}. You can place them in their own directories to allow users to easily navigate your website.
:::

## Advanced configuration

For a large projects, our `mkdocs-material` configuration file could look like this:

```yaml
site_name: Name of your package

nav:
- index.md # main page of your website
- contributing.md # contributing guide
- Tutorials: # tutorials
- tutorials/get_started.md
- tutorials/more_complex_usage.md
- Guides: # guides blog post
- guide/first_guide.md
- guide/second_guide.md
- Reference: # reference documentation
- reference/some_function.md
- reference/another_function.md

plugins:
- mkdocstrings
```

Our package would then have the following structure:

```
my_package/
├── my_package/
│ ├── __init__.py
│ ├── my_module.py
│ └── other_module.py
├── docs/
│ ├── index.md
│ ├── contributing.md
│ ├── reference/
│ ├── guides/
│ └── tutorials/
├── .git/
├── .venv/
├── mkdocs.yaml
├── .gitignore
├── README.md
├── LICENSE
└── pyproject.toml
```

This is of course just a very simple example and can be very different.

## Advanced customization

So far, we've written some simple Markdown code, but you can change many other things: set a different theme, use pretty user interface elements, add code snippets, and so on.

`mkdocs-material` has a ton of super cool customization options, that you can often configure with just one line in your `mkdocs.yaml` file. It is recommended to browse the [official website](https://squidfunk.github.io/mkdocs-material/){target="_blank"} for this purpose.

If you are interested in a pre made configuration template, check out the [Python package template](../../python-package-template.html).
Expand Down
2 changes: 1 addition & 1 deletion blog/create-a-package/handling-dependencies.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def read_file(filename):
```

::: {.callout-important}
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..
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 without optional dependencies.
:::

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!).
Expand Down
2 changes: 2 additions & 0 deletions blog/create-a-package/index.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Not really, especially if you take the time to read about it.

Making a Python package is mostly about organizing your project in a specific, standardized way. There are no low-level computer science concepts that you should know, but rather a more or less large set of rules to respect.

Note that the emphasis here is on best practices for developing high-quality software under the best conditions. Much of what you learn while developing Python packages will also help you become a better programmer in general.

## Is it only for large projects?

Not at all! Even if your project is 200 lines of code in a single file, it might make sense to make it a package. You can find a fun example [here](https://github.com/koaning/smartfunc){target="_blank"}.
Expand Down
Loading